/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.translator.solr; import static org.teiid.language.SQLConstants.Reserved.*; import static org.teiid.language.visitor.SQLStringVisitor.*; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Stack; import java.util.TimeZone; import org.apache.solr.client.solrj.SolrQuery; import org.teiid.core.types.DataTypeManager; import org.teiid.core.util.StringUtil; import org.teiid.language.*; import org.teiid.language.SQLConstants.Reserved; import org.teiid.language.SQLConstants.Tokens; import org.teiid.language.visitor.HierarchyVisitor; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.RuntimeMetadata; import org.teiid.translator.jdbc.FunctionModifier; public class SolrSQLHierarchyVistor extends HierarchyVisitor { private static SimpleDateFormat sdf; static { sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS'Z'"); //$NON-NLS-1$ sdf.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ } @SuppressWarnings("unused") private RuntimeMetadata metadata; protected StringBuilder buffer = new StringBuilder(); private List<String> fieldNameList = new ArrayList<String>(); protected Stack<String> onGoingExpression = new Stack<String>(); private boolean limitInUse; private SolrQuery query = new SolrQuery(); private SolrExecutionFactory ef; private HashMap<String, String> columnAliasMap = new HashMap<String, String>(); private boolean countStarInUse; public SolrSQLHierarchyVistor(RuntimeMetadata metadata, SolrExecutionFactory ef) { this.metadata = metadata; this.ef = ef; } @Override public void visit(DerivedColumn obj) { visitNode(obj.getExpression()); String expr = this.onGoingExpression.pop(); if (obj.getAlias() != null) { this.columnAliasMap.put(obj.getAlias(), expr); } query.addField(expr); fieldNameList.add(expr); } public static String getColumnName(ColumnReference obj) { String elemShortName = null; AbstractMetadataRecord elementID = obj.getMetadataObject(); if(elementID != null) { elemShortName = getRecordName(elementID); } else { elemShortName = obj.getName(); } return elemShortName; } @Override public void visit(ColumnReference obj) { if (obj.getMetadataObject() != null) { this.onGoingExpression.push(getColumnName(obj)); } else { this.onGoingExpression.push(this.columnAliasMap.get(getColumnName(obj))); } } /** * @return the full column names tableName.columnNames */ public List<String> getFieldNameList() { return fieldNameList; } /** * Note: Solr does not support <,> exclusively. It is always * <=, >= */ @Override public void visit(Comparison obj) { visitNode(obj.getLeftExpression()); String lhs = this.onGoingExpression.pop(); visitNode(obj.getRightExpression()); String rhs = this.onGoingExpression.pop(); if (lhs != null) { switch (obj.getOperator()) { case EQ: buffer.append(lhs).append(":").append(rhs); //$NON-NLS-1$ break; case NE: buffer.append(Reserved.NOT).append(Tokens.SPACE); buffer.append(lhs).append(Tokens.COLON).append(rhs); break; case LE: buffer.append(lhs).append(":[* TO"); //$NON-NLS-1$ buffer.append(Tokens.SPACE).append(rhs).append(Tokens.RSBRACE); break; case LT: buffer.append(lhs).append(":[* TO"); //$NON-NLS-1$ buffer.append(Tokens.SPACE).append(rhs).append(Tokens.RSBRACE); buffer.append(Tokens.SPACE).append(Reserved.AND).append(Tokens.SPACE); buffer.append(Reserved.NOT).append(Tokens.SPACE).append(lhs); buffer.append(Tokens.COLON).append(rhs); break; case GE: buffer.append(lhs).append(":[").append(rhs).append(" TO *]");//$NON-NLS-1$ //$NON-NLS-2$ break; case GT: buffer.append(lhs).append(":[").append(rhs); //$NON-NLS-1$ buffer.append(" TO *]").append(Tokens.SPACE).append(Reserved.AND).append(Tokens.SPACE); //$NON-NLS-1$ buffer.append(Reserved.NOT).append(Tokens.SPACE).append(lhs); buffer.append(Tokens.COLON).append(rhs); break; } } } @Override public void visit(AndOr obj) { // prepare statement buffer.append(Tokens.LPAREN); buffer.append(Tokens.LPAREN); // walk left node super.visitNode(obj.getLeftCondition()); buffer.append(Tokens.RPAREN); switch (obj.getOperator()) { case AND: buffer.append(Tokens.SPACE).append(Reserved.AND).append(Tokens.SPACE); break; case OR: buffer.append(Tokens.SPACE).append(Reserved.OR).append(Tokens.SPACE); break; } buffer.append(Tokens.LPAREN); //walk right node super.visitNode(obj.getRightCondition()); buffer.append(Tokens.RPAREN); buffer.append(Tokens.RPAREN); } @Override public void visit(In obj) { visitNode(obj.getLeftExpression()); String lhs = this.onGoingExpression.pop(); visitNodes(obj.getRightExpressions()); if (obj.isNegated()){ buffer.append(Reserved.NOT).append(Tokens.SPACE); } //start solr expression buffer.append(lhs).append(Tokens.COLON).append(Tokens.LPAREN); int i = obj.getRightExpressions().size(); while(i-- > 0) { //append rhs side as we iterates buffer.append(onGoingExpression.pop()); if(i > 0) { buffer.append(Tokens.SPACE).append(Reserved.OR).append(Tokens.SPACE); } } buffer.append(Tokens.RPAREN); } /** * @see org.teiid.language.visitor.HierarchyVisitor#visit(org.teiid.language.Like) * Description: transforms the like statements into solor syntax */ @Override public void visit(Like obj) { visitNode(obj.getLeftExpression()); String lhs = this.onGoingExpression.pop(); visitNode(obj.getRightExpression()); String rhs = this.onGoingExpression.pop(); if (obj.isNegated()){ buffer.append(Reserved.NOT).append(Tokens.SPACE); } buffer.append(lhs).append(Tokens.COLON).append(formatSolrQuery(rhs)); } @Override public void visit(Literal obj) { if (obj.getValue() == null) { buffer.append(NULL); } else { Class<?> type = obj.getType(); Object val = obj.getValue(); if(Number.class.isAssignableFrom(type)) { this.onGoingExpression.push(escapeString(String.valueOf(val))); } else if(type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) { this.onGoingExpression.push(obj.getValue().equals(Boolean.TRUE) ? TRUE : FALSE); } else if(type.equals(DataTypeManager.DefaultDataClasses.TIMESTAMP) || type.equals(DataTypeManager.DefaultDataClasses.TIME) || type.equals(DataTypeManager.DefaultDataClasses.DATE)) { synchronized (sdf) { this.onGoingExpression.push(escapeString(sdf.format(val))); } } else { this.onGoingExpression.push(escapeString(val.toString())); } } } /** * Creates a SQL-safe string. Simply replaces all occurrences of ' with '' * @param str the input string * @return a SQL-safe string */ protected String escapeString(String str) { // needs escaping + - && || ! ( ) { } [ ] ^ " ~ * ? : // source: http://khaidoan.wikidot.com/solr String[] array = {"+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ //$NON-NLS-13$ //$NON-NLS-14$ //$NON-NLS-15$ //$NON-NLS-16$ //$NON-NLS-17$ for (int i = 0; i < array.length; i++) { str = StringUtil.replaceAll(str, array[i], "\\" + array[i]); //$NON-NLS-1$ } return str; } @Override public void visit(Limit obj) { this.limitInUse = true; if (!countStarInUse) { this.query.setRows(obj.getRowLimit()); this.query.setStart(obj.getRowOffset()); } } @Override public void visit(OrderBy obj) { visitNodes(obj.getSortSpecifications()); } @Override public void visit(SortSpecification obj) { visitNode(obj.getExpression()); String expr = this.onGoingExpression.pop(); this.query.addSort(expr, obj.getOrdering() == SortSpecification.Ordering.ASC?SolrQuery.ORDER.asc:SolrQuery.ORDER.desc); } @Override public void visit(Function obj) { FunctionModifier funcModifier = this.ef.getFunctionModifiers().get(obj.getName()); if (funcModifier != null) { funcModifier.translate(obj); } StringBuilder sb = new StringBuilder(); visitNodes(obj.getParameters()); for (int i = 0; i < obj.getParameters().size(); i++) { sb.insert(0,this.onGoingExpression.pop()); if (i < obj.getParameters().size()-1) { sb.insert(0,Tokens.COMMA); } } sb.insert(0,Tokens.LPAREN); sb.insert(0,obj.getName()); sb.append(Tokens.RPAREN); this.onGoingExpression.push(sb.toString()); } @Override public void visit(AggregateFunction obj) { if (obj.getName().equals(AggregateFunction.COUNT)) { // this is only true for count(*) case, so we need implicit group id clause this.query.setRows(0); this.countStarInUse = true; this.onGoingExpression.push("1"); //$NON-NLS-1$ } else if (obj.getName().equals(AggregateFunction.AVG)) { } else if (obj.getName().equals(AggregateFunction.SUM)) { } else if (obj.getName().equals(AggregateFunction.MIN)) { } else if (obj.getName().equals(AggregateFunction.MAX)) { } else { } } private String formatSolrQuery(String solrQuery) { solrQuery = solrQuery.replace("%", "*"); //$NON-NLS-1$ //$NON-NLS-2$ solrQuery = solrQuery.replace("'",""); //$NON-NLS-1$ //$NON-NLS-2$ // solrQuery = solrQuery.replace("_", "?"); return solrQuery; } public SolrQuery getSolrQuery() { if (buffer == null || buffer.length() == 0) { buffer = new StringBuilder("*:*"); //$NON-NLS-1$ } return query.setQuery(buffer.toString()); } public boolean isLimitInUse() { return this.limitInUse; } public boolean isCountStarInUse() { return countStarInUse; } }