/* * 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.query.eval; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; import java.sql.SQLXML; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.teiid.core.types.ArrayImpl; import org.teiid.core.types.BaseLob; import org.teiid.core.types.BinaryType; import org.teiid.core.types.BlobType; import org.teiid.core.types.ClobType; import org.teiid.core.types.DataTypeManagerService; import org.teiid.core.types.InputStreamFactory; import org.teiid.core.types.SQLXMLImpl; import org.teiid.core.types.Sequencable; import org.teiid.core.types.Streamable; import org.teiid.core.types.XMLType; import org.teiid.core.types.XMLType.Type; import org.teiid.core.types.basic.StringToSQLXMLTransform; import org.teiid.core.util.StringUtil; import org.teiid.designer.annotation.Updated; import org.teiid.designer.query.sql.lang.ICompareCriteria; import org.teiid.designer.query.sql.lang.IMatchCriteria.MatchMode; import org.teiid.designer.query.sql.symbol.IElementSymbol; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version; import org.teiid.designer.udf.IFunctionLibrary; import org.teiid.language.SQLConstants; import org.teiid.metadata.FunctionMethod.PushDown; import org.teiid.query.function.FunctionDescriptor; import org.teiid.query.function.JSONFunctionMethods.JSONBuilder; import org.teiid.query.function.source.XMLSystemFunctions; import org.teiid.query.function.source.XMLSystemFunctions.XmlConcat; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.parser.TeiidNodeFactory; import org.teiid.query.parser.TeiidNodeFactory.ASTNodes; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.query.processor.relational.XMLTableNode; import org.teiid.query.sql.lang.AbstractSetCriteria; import org.teiid.query.sql.lang.CollectionValueIterator; import org.teiid.query.sql.lang.CompareCriteria; import org.teiid.query.sql.lang.CompoundCriteria; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.lang.ExistsCriteria; import org.teiid.query.sql.lang.ExpressionCriteria; import org.teiid.query.sql.lang.IsDistinctCriteria; import org.teiid.query.sql.lang.IsNullCriteria; import org.teiid.query.sql.lang.LanguageObject; import org.teiid.query.sql.lang.MatchCriteria; import org.teiid.query.sql.lang.NamespaceItem; import org.teiid.query.sql.lang.NotCriteria; import org.teiid.query.sql.lang.SetCriteria; import org.teiid.query.sql.lang.SubqueryCompareCriteria; import org.teiid.query.sql.lang.SubqueryCompareCriteria.PredicateQuantifier; import org.teiid.query.sql.lang.SubqueryContainer; import org.teiid.query.sql.lang.SubquerySetCriteria; import org.teiid.query.sql.proc.ExceptionExpression; import org.teiid.query.sql.symbol.AggregateSymbol; import org.teiid.query.sql.symbol.AliasSymbol; import org.teiid.query.sql.symbol.Array; import org.teiid.query.sql.symbol.CaseExpression; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.DerivedColumn; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.ExpressionSymbol; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.symbol.JSONObject; import org.teiid.query.sql.symbol.QueryString; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.symbol.ScalarSubquery; import org.teiid.query.sql.symbol.SearchedCaseExpression; import org.teiid.query.sql.symbol.TextLine; import org.teiid.query.sql.symbol.WindowFunction; import org.teiid.query.sql.symbol.XMLCast; import org.teiid.query.sql.symbol.XMLElement; import org.teiid.query.sql.symbol.XMLExists; import org.teiid.query.sql.symbol.XMLForest; import org.teiid.query.sql.symbol.XMLNamespaces; import org.teiid.query.sql.symbol.XMLParse; import org.teiid.query.sql.symbol.XMLQuery; import org.teiid.query.sql.symbol.XMLSerialize; import org.teiid.query.sql.util.ValueIterator; import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor; import org.teiid.query.util.CommandContext; import org.teiid.query.util.VariableContext; import org.teiid.query.xquery.saxon.SaxonXQueryExpression; import org.teiid.query.xquery.saxon.SaxonXQueryExpression.Result; import org.teiid.query.xquery.saxon.SaxonXQueryExpression.RowProcessor; import org.teiid.query.xquery.saxon.XQueryEvaluator; import org.teiid.runtime.client.Messages; import org.teiid.runtime.client.TeiidClientException; import org.teiid.runtime.client.query.SyntaxFactory; import org.teiid.translator.SourceSystemFunctions; import net.sf.saxon.Configuration; import net.sf.saxon.om.Item; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.query.QueryResult; import net.sf.saxon.trans.XPathException; import net.sf.saxon.value.StringValue; import net.sf.saxon.value.StringValue; /** * */ public class Evaluator { private final class XMLQueryRowProcessor implements RowProcessor { XmlConcat concat; //just used to get a writer Type type; private javax.xml.transform.Result result; boolean hasItem; private XMLQueryRowProcessor() throws Exception { this(false); } private XMLQueryRowProcessor(boolean exists) throws Exception { if (! exists) { concat = new XmlConcat(); result = new StreamResult(concat.getWriter()); } } @Override public void processRow(NodeInfo row) { if (concat == null) { // // concat will never be null for versions less than 10 // since they use default constructor // hasItem = true; return; } if (type == null) { type = SaxonXQueryExpression.getType(row); } else { type = Type.CONTENT; } try { QueryResult.serialize(row, result, SaxonXQueryExpression.DEFAULT_OUTPUT_PROPERTIES); } catch (XPathException e) { throw new RuntimeException(e); } } } private final class SequenceReader extends Reader { private LinkedList<Reader> readers; private Reader current = null; public SequenceReader(LinkedList<Reader> readers) { this.readers = readers; } @Override public void close() { for (Reader reader : readers) { try { reader.close(); } catch (IOException e) { } } } @Override public int read(char[] cbuf, int off, int len) throws IOException { if (current == null && !readers.isEmpty()) { current = readers.removeFirst(); } if (current == null) { return -1; } int read = current.read(cbuf, off, len); if (read == -1) { current.close(); current = null; read = 0; } if (read < len) { int nextRead = read(cbuf, off + read, len - read); if (nextRead > 0) { read += nextRead; } } return read; } } @SuppressWarnings( "javadoc" ) public static class NameValuePair<T> { public String name; public T value; public NameValuePair(String name, T value) { this.name = name; this.value = value; } } /** * * @param <T> */ public static interface ValueExtractor<T> { /** * @param t * @return extracted value */ Object getValue(T t); } private final static char[] REGEX_RESERVED = new char[] {'$', '(', ')', '*', '+', '.', '?', '[', '\\', ']', '^', '{', '|', '}'}; //in sorted order private final static MatchCriteria.PatternTranslator LIKE_TO_REGEX = new MatchCriteria.PatternTranslator(new char[] {'%', '_'}, new String[] {".*", "."}, REGEX_RESERVED, '\\', Pattern.DOTALL); //$NON-NLS-1$ //$NON-NLS-2$ private final static char[] SIMILAR_REGEX_RESERVED = new char[] {'$', '.', '\\', '^'}; //in sorted order private final static MatchCriteria.PatternTranslator SIMILAR_TO_REGEX = new MatchCriteria.PatternTranslator( new char[] {'%', '(', ')', '*', '?', '+', '[', ']', '_', '{', '|', '}'}, new String[] {"([a]|[^a])*", "(", ")", "*", "?", "+", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ "[", "]", "([a]|[^a])", "{", "|", "}"}, SIMILAR_REGEX_RESERVED, '\\', 0); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ private final CommandContext context; /** * @param teiidVersion */ public Evaluator(ITeiidServerVersion teiidVersion) { context = new CommandContext(teiidVersion); } /** * @return teiid version */ public ITeiidServerVersion getTeiidVersion() { return context.getTeiidVersion(); } /** * @param criteria * @return evaluation of criteria * @throws Exception */ public static boolean assess(Criteria criteria) throws Exception { return new Evaluator(criteria.getTeiidVersion()).evaluate(criteria); } /** * @param expression * @return evaluation of expression * @throws Exception */ public static Object assess(Expression expression) throws Exception { return new Evaluator(expression.getTeiidVersion()).evaluate(expression); } /** * @param criteria * @return evaluation of criteria * @throws Exception */ public boolean evaluate(Criteria criteria) throws Exception { return Boolean.TRUE.equals(evaluateTVL(criteria)); } private Boolean evaluateTVL(Criteria criteria) throws Exception { return internalEvaluateTVL(criteria); } private Boolean internalEvaluateTVL(Criteria criteria) throws Exception { if(criteria instanceof CompoundCriteria) { return evaluate((CompoundCriteria)criteria); } else if(criteria instanceof NotCriteria) { return evaluate((NotCriteria)criteria); } else if(criteria instanceof CompareCriteria) { return evaluate((CompareCriteria)criteria); } else if(criteria instanceof MatchCriteria) { return evaluate((MatchCriteria)criteria); } else if(criteria instanceof AbstractSetCriteria) { return evaluate((AbstractSetCriteria)criteria); } else if(criteria instanceof IsNullCriteria) { return Boolean.valueOf(evaluate((IsNullCriteria)criteria)); } else if(criteria instanceof SubqueryCompareCriteria) { return evaluate((SubqueryCompareCriteria)criteria); } else if(criteria instanceof ExistsCriteria) { return Boolean.valueOf(evaluate((ExistsCriteria)criteria)); } else if (criteria instanceof ExpressionCriteria) { return (Boolean)evaluate(((ExpressionCriteria)criteria).getExpression()); } else if (criteria instanceof XMLExists) { return (Boolean) evaluateXMLQuery(((XMLExists)criteria).getXmlQuery(), true); } else if (criteria instanceof IsDistinctCriteria) { IsDistinctCriteria idc = (IsDistinctCriteria)criteria; TempMetadataID left = (TempMetadataID)idc.getLeftRowValue().getMetadataID(); TempMetadataID right = (TempMetadataID)idc.getRightRowValue().getMetadataID(); VariableContext vc = this.context.getVariableContext(); List<TempMetadataID> cols = left.getElements(); List<TempMetadataID> colsOther = right.getElements(); if (cols.size() != colsOther.size()) { return !idc.isNegated(); } SyntaxFactory factory = new SyntaxFactory(criteria.getTeiidVersion()); for (int i = 0; i < cols.size(); i++) { IElementSymbol symbol1 = factory.createElementSymbol(cols.get(i).getName()); symbol1.setGroupSymbol(idc.getLeftRowValue()); Object l = vc.getValue(symbol1); IElementSymbol symbol2 = factory.createElementSymbol(colsOther.get(i).getName()); symbol1.setGroupSymbol(idc.getRightRowValue()); Object r = vc.getValue(symbol2); if (l == null) { if (r != null) { if (idc.isNegated()) { return false; } return true; } } else if (r == null) { if (idc.isNegated()) { return false; } return true; } try { Boolean b = compare(CompareCriteria.EQ, l, r); if (b == null) { continue; //shouldn't happen } if (!b) { if (idc.isNegated()) { return false; } return true; } } catch (Exception e) { //we'll consider this a difference //more than likely they are different types if (idc.isNegated()) { return false; } return true; } } if (idc.isNegated()) { return true; } return false; } else { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30311, criteria)); } } private Boolean evaluate(CompoundCriteria criteria) throws Exception { List<Criteria> subCrits = criteria.getCriteria(); boolean and = criteria.getOperator() == CompoundCriteria.AND; Boolean result = and?Boolean.TRUE:Boolean.FALSE; for (int i = 0; i < subCrits.size(); i++) { Criteria subCrit = subCrits.get(i); Boolean value = internalEvaluateTVL(subCrit); if (value == null) { result = null; } else if (!value.booleanValue()) { if (and) { return Boolean.FALSE; } } else if (!and) { return Boolean.TRUE; } } return result; } private Boolean evaluate(NotCriteria criteria) throws Exception { Criteria subCrit = criteria.getCriteria(); Boolean result = internalEvaluateTVL(subCrit); if (result == null) { return null; } if (result.booleanValue()) { return Boolean.FALSE; } return Boolean.TRUE; } private Boolean evaluate(CompareCriteria criteria) throws Exception { // Evaluate left expression Object leftValue = null; try { leftValue = evaluate(criteria.getLeftExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30312, "left", criteria)); //$NON-NLS-1$ } // Shortcut if null if(leftValue == null) { return null; } // Evaluate right expression Object rightValue = null; try { rightValue = evaluate(criteria.getRightExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30312, "right", criteria)); //$NON-NLS-1$ } // Shortcut if null if(rightValue == null) { return null; } // Compare two non-null values using specified operator return compare(criteria.getOperator(), leftValue, rightValue); } private Boolean evaluate(MatchCriteria criteria) throws Exception { boolean result = false; // Evaluate left expression Object value = null; try { value = evaluate(criteria.getLeftExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30312, "left", criteria)); //$NON-NLS-1$ } // Shortcut if null if(value == null) { return null; } CharSequence leftValue = null; if (value instanceof CharSequence) { leftValue = (CharSequence)value; } else { try { leftValue = ((Sequencable)value).getCharSequence(); } catch (SQLException err) { throw new TeiidClientException(err, err.getMessage()); } } // Evaluate right expression String rightValue = null; try { rightValue = (String) evaluate(criteria.getRightExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30312, "right", criteria)); //$NON-NLS-1$ } // Shortcut if null if(rightValue == null) { return null; } result = match(rightValue, criteria.getEscapeChar(), leftValue, criteria.getMode()); return Boolean.valueOf(result ^ criteria.isNegated()); } private boolean match(String pattern, char escape, CharSequence search, MatchMode mode) throws Exception { Pattern patternRegex = null; switch (mode) { case LIKE: patternRegex = LIKE_TO_REGEX.translate(pattern, escape); break; case SIMILAR: patternRegex = SIMILAR_TO_REGEX.translate(pattern, escape); break; case REGEX: patternRegex = MatchCriteria.getPattern(pattern, pattern, 0); break; default: throw new AssertionError(); } Matcher matcher = patternRegex.matcher(search); return matcher.find(); } private Boolean evaluate(AbstractSetCriteria criteria) throws Exception { ITeiidServerVersion teiidVersion = criteria.getTeiidVersion(); // Evaluate expression Object leftValue = null; try { leftValue = evaluate(criteria.getExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30323, criteria)); } Boolean result = Boolean.FALSE; ValueIterator valueIter = null; if (criteria instanceof SetCriteria) { SetCriteria set = (SetCriteria)criteria; // Shortcut if null if(leftValue == null) { if (!set.getValues().isEmpty()) { return null; } return criteria.isNegated(); } if (set.isAllConstants()) { Constant c = TeiidNodeFactory.createASTNode(teiidVersion, ASTNodes.CONSTANT); c.setValue(leftValue); c.setType(criteria.getExpression().getType()); boolean exists = set.getValues().contains(c); if (!exists) { if (set.getValues().contains(Constant.getNullConstant(teiidVersion))) { return null; } return criteria.isNegated(); } return !criteria.isNegated(); } valueIter = new CollectionValueIterator(((SetCriteria)criteria).getValues()); } else if (criteria instanceof SubquerySetCriteria) { try { valueIter = evaluateSubquery((SubquerySetCriteria)criteria); } catch (Exception e) { throw new TeiidClientException(e); } } else { throw new AssertionError("unknown set criteria type"); //$NON-NLS-1$ } while(valueIter.hasNext()) { if(leftValue == null) { return null; } Object possibleValue = valueIter.next(); Object value = null; if(possibleValue instanceof Expression) { try { value = evaluate((Expression) possibleValue); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30323, possibleValue)); } } else { value = possibleValue; } if(value != null) { if(Constant.COMPARATOR.compare(leftValue, value) == 0) { return Boolean.valueOf(!criteria.isNegated()); } // else try next value } else { result = null; } } if (result == null) { return null; } return Boolean.valueOf(criteria.isNegated()); } private boolean evaluate(IsNullCriteria criteria) throws Exception { // Evaluate expression Object value = null; try { value = evaluate(criteria.getExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30323, criteria)); } return (value == null ^ criteria.isNegated()); } private Boolean evaluate(SubqueryCompareCriteria criteria) throws Exception { // Evaluate expression Object leftValue = null; try { leftValue = evaluate(criteria.getLeftExpression()); } catch(Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30323, criteria)); } // Need to be careful to initialize this variable carefully for the case // where valueIterator has no values, and the block below is not entered. // If there are no rows, and ALL is the predicate quantifier, the result // should be true. If SOME is the predicate quantifier, or no quantifier // is used, the result should be false. Boolean result = Boolean.FALSE; if (criteria.getPredicateQuantifier() == PredicateQuantifier.ALL){ result = Boolean.TRUE; } ValueIterator valueIter; try { valueIter = evaluateSubquery(criteria); } catch (Exception e) { throw new TeiidClientException(e); } while(valueIter.hasNext()) { Object value = valueIter.next(); // Shortcut if null if(leftValue == null) { return null; } if(value != null) { Boolean comp = compare(criteria.getOperator(), leftValue, value); switch(criteria.getPredicateQuantifier()) { case ALL: if (Boolean.FALSE.equals(comp)){ return Boolean.FALSE; } break; case SOME: if (Boolean.TRUE.equals(comp)){ return Boolean.TRUE; } break; default: throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30326, criteria.getPredicateQuantifier())); } } else { // value is null switch(criteria.getPredicateQuantifier()) { case ALL: if (Boolean.TRUE.equals(result)){ result = null; } break; case SOME: if (Boolean.FALSE.equals(result)){ result = null; } break; default: throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30326, criteria.getPredicateQuantifier())); } } } //end value iteration return result; } /** * @param operator type * @param leftValue * @param value * @return true if left value matches the value according to the criteria * @throws AssertionError */ @Updated(version=Version.TEIID_8_12_4) public static Boolean compare(int operator, Object leftValue, Object value) throws AssertionError { int compare = 0; //TODO: we follow oracle style array comparison //semantics. each element is treated as an individual comparison, //so null implies unknown. h2 (and likely other dbms) allow for null //array element equality if (leftValue instanceof ArrayImpl) { ArrayImpl av = (ArrayImpl)leftValue; try { compare = av.compareTo((ArrayImpl)value, true, Constant.COMPARATOR); } catch (ArrayImpl.NullException e) { return null; } } else { compare = Constant.COMPARATOR.compare(leftValue, value); } // Compare two non-null values using specified operator Boolean result = null; switch(operator) { case ICompareCriteria.EQ: result = Boolean.valueOf(compare == 0); break; case ICompareCriteria.NE: result = Boolean.valueOf(compare != 0); break; case ICompareCriteria.LT: result = Boolean.valueOf(compare < 0); break; case ICompareCriteria.LE: result = Boolean.valueOf(compare <= 0); break; case ICompareCriteria.GT: result = Boolean.valueOf(compare > 0); break; case ICompareCriteria.GE: result = Boolean.valueOf(compare >= 0); break; default: throw new AssertionError(); } return result; } private boolean evaluate(ExistsCriteria criteria) throws Exception { ValueIterator valueIter; try { valueIter = evaluateSubquery(criteria); } catch (Exception e) { throw new TeiidClientException(e); } if(valueIter.hasNext()) { return !criteria.isNegated(); } return criteria.isNegated(); } /** * @param expression * @return evaluated value of the given expression * @throws Exception */ public Object evaluate(Expression expression) throws Exception { try { return internalEvaluate(expression); } catch (Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30328, new Object[] {expression, e.getMessage()})); } } protected Object internalEvaluate(Expression expression) throws Exception { if(expression instanceof AggregateSymbol || expression instanceof AliasSymbol || expression instanceof ElementSymbol || expression instanceof WindowFunction) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30328, expression, Messages.getString(Messages.Misc.Evaluator_noValue))); } // ExpressionSymbol so we just need to dive in and evaluate the expression itself if (expression instanceof ExpressionSymbol) { ExpressionSymbol exprSyb = (ExpressionSymbol) expression; Expression expr = exprSyb.getExpression(); return internalEvaluate(expr); } if(expression instanceof Constant) { return ((Constant) expression).getValue(); } else if(expression instanceof Function) { return evaluate((Function) expression); } else if(expression instanceof CaseExpression) { return evaluate((CaseExpression) expression); } else if(expression instanceof SearchedCaseExpression) { return evaluate((SearchedCaseExpression) expression); } else if(expression instanceof Reference) { Reference ref = (Reference)expression; if (ref.isPositional() && ref.getExpression() == null) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30328, expression, Messages.getString(Messages.Misc.Evaluator_noValue))); } Object result = internalEvaluate(ref.getExpression()); if (ref.getConstraint() != null) { try { ref.getConstraint().validate(result); } catch (Exception e) { throw new TeiidClientException(e); } } return result; } else if(expression instanceof Criteria) { return evaluate((Criteria) expression); } else if(expression instanceof ScalarSubquery) { return evaluate((ScalarSubquery) expression); } else if (expression instanceof Criteria) { return evaluate((Criteria)expression); } else if (expression instanceof TextLine){ return evaluateTextLine((TextLine)expression); } else if (expression instanceof XMLElement){ return evaluateXMLElement((XMLElement)expression); } else if (expression instanceof XMLForest){ return evaluateXMLForest((XMLForest)expression); } else if (expression instanceof JSONObject){ return evaluateJSONObject((JSONObject)expression, null); } else if (expression instanceof XMLSerialize){ return evaluateXMLSerialize((XMLSerialize)expression); } else if (expression instanceof XMLQuery) { return evaluateXMLQuery((XMLQuery)expression, false); } else if (expression instanceof QueryString) { return evaluateQueryString((QueryString)expression); } else if (expression instanceof XMLParse){ return evaluateXMLParse((XMLParse)expression); } else if (expression instanceof Array) { Array array = (Array)expression; List<Expression> exprs = array.getExpressions(); Object[] result = (Object[]) java.lang.reflect.Array.newInstance(array.getComponentType(), exprs.size()); for (int i = 0; i < exprs.size(); i++) { Object eval = internalEvaluate(exprs.get(i)); if (eval instanceof ArrayImpl) { eval = ((ArrayImpl)eval).getValues(); } result[i] = eval; } return new ArrayImpl(expression.getTeiidVersion(), result); } else if (expression instanceof ExceptionExpression) { return evaluate((ExceptionExpression)expression); } else if (expression instanceof XMLCast) { return evaluate((XMLCast)expression); } else { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30329, expression.getClass().getName())); } } private Object evaluate(XMLCast expression) throws Exception { Object val = internalEvaluate(expression.getExpression()); if (val == null) { TeiidNodeFactory factory = new TeiidNodeFactory(); Constant constant = factory.create(expression.getTeiidVersion(), ASTNodes.CONSTANT); constant.setValue(null); constant.setType(expression.getType()); return constant; } Configuration config = new Configuration(); XMLType value = (XMLType)val; Type t = value.getType(); try { Item i = null; switch (t) { case CONTENT: //content could map to an array value, but we aren't handling that case here yet - only in xmltable case COMMENT: case PI: throw new TeiidClientException(); case TEXT: i = new StringValue(value.getString()); break; case UNKNOWN: case DOCUMENT: case ELEMENT: StreamSource ss = value.getSource(StreamSource.class); try { i = config.buildDocument(ss); } finally { if (ss.getInputStream() != null) { ss.getInputStream().close(); } if (ss.getReader() != null) { ss.getReader().close(); } } break; default: throw new AssertionError("Unknown xml value type " + t); //$NON-NLS-1$ } return XMLTableNode.getValue(expression.getType(), i, config, context); } catch (Exception e) { throw new TeiidClientException(e); } } @SuppressWarnings( "unused" ) private Object evaluate(ExceptionExpression ee) throws Exception { String msg = (String) internalEvaluate(ee.getMessage()); String sqlState = ee.getDefaultSQLState(); if (ee.getSqlState() != null) { sqlState = (String) internalEvaluate(ee.getSqlState()); } Integer errorCode = null; if (ee.getErrorCode() != null) { errorCode = (Integer) internalEvaluate(ee.getErrorCode()); } Exception parent = null; if (ee.getParent() != null) { parent = (Exception) internalEvaluate(ee.getParent()); } msg = errorCode != null ? errorCode + " : " + msg : msg; //$NON-NLS-1$ Exception result = new TeiidClientException(parent, msg); return result; } private Object evaluateXMLParse(final XMLParse xp) throws Exception { Object value = internalEvaluate(xp.getExpression()); if (value == null) { return null; } XMLType.Type type = Type.DOCUMENT; SQLXMLImpl result = null; try { if (value instanceof String) { String string = (String)value; result = new SQLXMLImpl(string); result.setCharset(Streamable.CHARSET); if (!xp.isWellFormed()) { Reader r = new StringReader(string); type = validate(xp, r); } } else if (value instanceof BinaryType ) { BinaryType string = (BinaryType)value; result = new SQLXMLImpl(string.getBytesDirect()); result.setCharset(Streamable.CHARSET); if (!xp.isWellFormed()) { Reader r = result.getCharacterStream(); type = validate(xp, r); } } else { InputStreamFactory isf = null; Streamable<?> s = (Streamable<?>)value; isf = getInputStreamFactory(s); result = new SQLXMLImpl(isf); if (!xp.isWellFormed()) { Reader r = result.getCharacterStream(); type = validate(xp, r); } } } catch (Exception e) { throw new TeiidClientException(e); } if (!xp.isDocument()) { type = Type.CONTENT; } XMLType xml = new XMLType(result); xml.setType(type); return xml; } /** * @param s * @return input stream factory */ public static InputStreamFactory getInputStreamFactory(Streamable<?> s) { if (s.getReference() instanceof Streamable<?>) { return getInputStreamFactory((Streamable<?>) s.getReference()); } if (s.getReference() instanceof BaseLob) { BaseLob bl = (BaseLob) s.getReference(); try { InputStreamFactory isf = bl.getStreamFactory(); if (isf != null) { return isf; } } catch (SQLException e) { } } if (s instanceof ClobType) { return new InputStreamFactory.ClobInputStreamFactory((Clob)s.getReference()); } else if (s instanceof BlobType){ return new InputStreamFactory.BlobInputStreamFactory((Blob)s.getReference()); } return new InputStreamFactory.SQLXMLInputStreamFactory((SQLXML)s.getReference()); } private Type validate(final XMLParse xp, Reader r) throws Exception { if (!xp.isDocument()) { LinkedList<Reader> readers = new LinkedList<Reader>(); readers.add(new StringReader("<r>")); //$NON-NLS-1$ readers.add(r); readers.add(new StringReader("</r>")); //$NON-NLS-1$ r = new SequenceReader(readers); } return StringToSQLXMLTransform.isXml(r); } /** * Taken from WSConnection * * @param s * @return */ private String httpURLEncode(String s) { try { return URLEncoder.encode(s, "UTF-8").replaceAll("\\+", "%20"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } //TODO: exception if length is too long? private Object evaluateQueryString(QueryString queryString) throws Exception { Evaluator.NameValuePair<Object>[] pairs = getNameValuePairs(queryString.getArgs(), false, true); String path = (String)internalEvaluate(queryString.getPath()); if (path == null) { path = ""; //$NON-NLS-1$ } boolean appendedAny = false; StringBuilder result = new StringBuilder(); for (Evaluator.NameValuePair<Object> nameValuePair : pairs) { if (nameValuePair.value == null) { continue; } if (appendedAny) { result.append('&'); } appendedAny = true; result.append(httpURLEncode(nameValuePair.name)).append('=').append(httpURLEncode((String)nameValuePair.value)); } if (!appendedAny) { return path; } result.insert(0, '?'); result.insert(0, path); return result.toString(); } /** * * @param xmlQuery * @param exists - check only for the existence of a non-empty result * @return Boolean if exists is true, otherwise an XMLType value * @throws BlockedException * @throws TeiidComponentException * @throws FunctionExecutionException */ private Object evaluateXMLQuery(XMLQuery xmlQuery, boolean exists) throws Exception { boolean emptyOnEmpty = xmlQuery.getEmptyOnEmpty() == null || xmlQuery.getEmptyOnEmpty(); Result result = null; try { XMLQueryRowProcessor rp = null; ITeiidServerVersion teiidVersion = xmlQuery.getTeiidVersion(); if (xmlQuery.getXQueryExpression().isStreaming()) { rp = new XMLQueryRowProcessor(exists); } try { result = evaluateXQuery(xmlQuery.getXQueryExpression(), xmlQuery.getPassing(), rp); if (result == null) { return null; } if (exists) { if (result.iter.next() == null) { return false; } return true; } } catch (RuntimeException e) { if (e.getCause() instanceof XPathException) { throw (XPathException)e.getCause(); } throw e; } if (rp != null) { if (exists) { return rp.hasItem; } XMLType.Type type = rp.type; if (type == null) { if (!emptyOnEmpty) { return null; } type = Type.CONTENT; } XMLType val = rp.concat.close(context); val.setType(rp.type); return val; } return xmlQuery.getXQueryExpression().createXMLType(result.iter, emptyOnEmpty, context); } catch (Exception e) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30333, e.getMessage())); } finally { if (result != null) { result.close(); } } } private Object evaluateXMLSerialize(XMLSerialize xs) throws Exception { XMLType value = (XMLType) internalEvaluate(xs.getExpression()); if (value == null) { return null; } try { if (!xs.getDocument()) { return XMLSystemFunctions.serialize(xs, value); } if (value.getType() == Type.UNKNOWN) { Type type = StringToSQLXMLTransform.isXml(value.getCharacterStream()); value.setType(type); } if (value.getType() == Type.DOCUMENT || value.getType() == Type.ELEMENT) { return XMLSystemFunctions.serialize(xs, value); } } catch (Exception e) { throw new TeiidClientException(e); } throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30336)); } /** * Taken from TextLine */ private static ValueExtractor<NameValuePair<Object>> defaultExtractor = new ValueExtractor<NameValuePair<Object>>() { @Override public Object getValue(NameValuePair<Object> t) { return t.value; } }; /** * Taken from TextLine */ private <T> String[] evaluate(final List<T> values, ValueExtractor<T> valueExtractor, TextLine textLine) throws Exception { Character delimeter = textLine.getDelimiter(); if (delimeter == null) { delimeter = Character.valueOf(','); } String delim = String.valueOf(delimeter.charValue()); Character quote = textLine.getQuote(); String quoteStr = null; if (quote == null) { quoteStr = "\""; //$NON-NLS-1$ } else { quoteStr = String.valueOf(quote); } String doubleQuote = quoteStr + quoteStr; ArrayList<String> result = new ArrayList<String>(); DataTypeManagerService dataTypeManager = DataTypeManagerService.getInstance(textLine.getTeiidVersion()); for (Iterator<T> iterator = values.iterator(); iterator.hasNext();) { T t = iterator.next(); String text = (String)dataTypeManager.transformValue( valueExtractor.getValue(t), DataTypeManagerService.DefaultDataTypes.STRING.getTypeClass()); if (text == null) { continue; } result.add(quoteStr); result.add(StringUtil.replaceAll(text, quoteStr, doubleQuote)); result.add(quoteStr); if (iterator.hasNext()) { result.add(delim); } } result.add(textLine.getLineEnding()); return result.toArray(new String[result.size()]); } private Object evaluateTextLine(TextLine function) throws Exception { List<DerivedColumn> args = function.getExpressions(); Evaluator.NameValuePair<Object>[] nameValuePairs = getNameValuePairs(args, true, true); try { return new ArrayImpl(function.getTeiidVersion(), evaluate(Arrays.asList(nameValuePairs), defaultExtractor, function)); } catch (Exception e) { throw new TeiidClientException(e); } } private Object evaluateXMLForest(XMLForest function) throws Exception { List<DerivedColumn> args = function.getArgs(); Evaluator.NameValuePair<Object>[] nameValuePairs = getNameValuePairs(args, true, true); try { return XMLSystemFunctions.xmlForest(context, namespaces(function.getNamespaces()), nameValuePairs); } catch (Exception e) { throw new TeiidClientException(e); } } private Object evaluateJSONObject(JSONObject function, JSONBuilder builder) throws Exception { List<DerivedColumn> args = function.getArgs(); Evaluator.NameValuePair<Object>[] nameValuePairs = getNameValuePairs(args, false, false); boolean returnValue = false; try { if (builder == null) { returnValue = true; //preevaluate subqueries to prevent blocked exceptions for (SubqueryContainer<?> container : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(function)) { evaluateSubquery(container); } builder = new JSONBuilder(function.getTeiidVersion()); } builder.start(false); for (NameValuePair<Object> nameValuePair : nameValuePairs) { addValue(builder, nameValuePair.name, nameValuePair.value); } builder.end(false); if (returnValue) { ClobType result = builder.close(context); builder = null; return result; } return null; } catch (Exception e) { throw new TeiidClientException(e); } finally { if (returnValue && builder != null) { builder.remove(); } } } private void addValue(JSONBuilder builder, String name, Object value) throws Exception { try { if (value instanceof JSONObject) { builder.startValue(name); evaluateJSONObject((JSONObject)value, builder); return; } if (value instanceof Function) { Function f = (Function)value; if (IFunctionLibrary.FunctionName.JSONARRAY.equalsIgnoreCase(f.getName())) { builder.startValue(name); jsonArray(context, f, f.getArgs(), builder, this); return; } } builder.addValue(name, internalEvaluate((Expression)value)); } catch (Exception e) { throw e; } } /** * @param f * @param vals * @param builder * @param eval * @return json clob * @throws Exception */ public static ClobType jsonArray(CommandContext context, Function f, Object[] vals, JSONBuilder builder, Evaluator eval) throws Exception { boolean returnValue = false; try { if (builder == null) { returnValue = true; if (eval != null) { //preevaluate subqueries to prevent blocked exceptions for (SubqueryContainer<?> container : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(f)) { eval.evaluateSubquery(container); } } builder = new JSONBuilder(f.getTeiidVersion()); } builder.start(true); for (Object object : vals) { if (eval != null) { eval.addValue(builder, null, object); } else { builder.addValue(object); } } builder.end(true); if (returnValue) { ClobType result = builder.close(context); builder = null; return result; } return null; } finally { if (returnValue && builder != null) { builder.remove(); } } } private Object evaluateXMLElement(XMLElement function) throws Exception { List<Expression> content = function.getContent(); List<Object> values = new ArrayList<Object>(content.size()); for (Expression exp : content) { values.add(internalEvaluate(exp)); } try { Evaluator.NameValuePair<Object>[] attributes = null; if (function.getAttributes() != null) { attributes = getNameValuePairs(function.getAttributes().getArgs(), true, true); } return XMLSystemFunctions.xmlElement(function.getName(), namespaces(function.getNamespaces()), attributes, values, context); } catch (Exception e) { throw new TeiidClientException(e); } } private Result evaluateXQuery(SaxonXQueryExpression xquery, List<DerivedColumn> cols, RowProcessor processor) throws Exception { Map<String, Object> parameters = new HashMap<String, Object>(); Object contextItem = null; evaluateParameters(cols, parameters); if (parameters.containsKey(null)) { contextItem = parameters.remove(null); if (contextItem == null) { return null; } } return XQueryEvaluator.evaluateXQuery(xquery, contextItem, parameters, processor); } /** * Evaluate the parameters and return the context item if it exists * @param cols * @param parameters * @return context item * @throws Exception */ public Object evaluateParameters(List<DerivedColumn> cols, Map<String, Object> parameters) throws Exception { Object contextItem = null; for (DerivedColumn passing : cols) { Object value = evaluateParameter(passing); if (passing.getAlias() == null) { parameters.put(null, value); } else { parameters.put(passing.getAlias(), value); } } return contextItem; } private Object evaluateParameter(DerivedColumn passing) throws Exception { if (passing.getExpression() instanceof Function) { Function f = (Function)passing.getExpression(); //narrow optimization of json based documents to allow for lower overhead streaming if (f.getName().equalsIgnoreCase(SourceSystemFunctions.JSONTOXML)) { String rootName = (String)this.evaluate(f.getArg(0)); Object lob = this.evaluate(f.getArg(1)); if (rootName == null || lob == null) { return null; } try { if (lob instanceof Blob) { return XMLSystemFunctions.jsonToXml(context, rootName, (Blob)lob, true); } return XMLSystemFunctions.jsonToXml(context, rootName, (Clob)lob, true); } catch (Exception e) { throw new TeiidClientException(e, Messages.gs(Messages.TEIID.TEIID30384, f.getFunctionDescriptor().getName())); } } } else if (passing.getExpression() instanceof XMLParse) { XMLParse xmlParse = (XMLParse)passing.getExpression(); xmlParse.setWellFormed(true); } Object value = this.evaluate(passing.getExpression()); return value; } private Evaluator.NameValuePair<Object>[] getNameValuePairs(List<DerivedColumn> args, boolean xmlNames, boolean eval) throws Exception { Evaluator.NameValuePair<Object>[] nameValuePairs = new Evaluator.NameValuePair[args.size()]; for (int i = 0; i < args.size(); i++) { DerivedColumn symbol = args.get(i); String name = symbol.getAlias(); Expression ex = symbol.getExpression(); if (name == null && ex instanceof ElementSymbol) { name = ((ElementSymbol)ex).getShortName(); } if (name != null) { if (xmlNames) { name = XMLSystemFunctions.escapeName(name, true); } } else if (!xmlNames) { name = "expr" + (i+1); //$NON-NLS-1$ } nameValuePairs[i] = new Evaluator.NameValuePair<Object>(name, eval?internalEvaluate(ex):ex); } return nameValuePairs; } private Evaluator.NameValuePair<String>[] namespaces(XMLNamespaces namespaces) { if (namespaces == null) { return null; } List<NamespaceItem> args = namespaces.getNamespaceItems(); Evaluator.NameValuePair<String>[] nameValuePairs = new Evaluator.NameValuePair[args.size()]; for(int i=0; i < args.size(); i++) { NamespaceItem item = args.get(i); nameValuePairs[i] = new Evaluator.NameValuePair<String>(item.getPrefix(), item.getUri()); } return nameValuePairs; } private Object evaluate(CaseExpression expr) throws Exception { Object exprVal = internalEvaluate(expr.getExpression()); for (int i = 0; i < expr.getWhenCount(); i++) { Object intEvObj = internalEvaluate(expr.getWhenExpression(i)); if (intEvObj != null && exprVal != null && intEvObj.equals(exprVal)) { return internalEvaluate(expr.getThenExpression(i)); } } if (expr.getElseExpression() != null) { return internalEvaluate(expr.getElseExpression()); } return null; } private Object evaluate(SearchedCaseExpression expr) throws Exception { for (int i = 0; i < expr.getWhenCount(); i++) { if (evaluate(expr.getWhenCriteria(i))) { return internalEvaluate(expr.getThenExpression(i)); } } if (expr.getElseExpression() != null) { return internalEvaluate(expr.getElseExpression()); } return null; } private Object evaluate(Function function) throws Exception { // Get function based on resolved function info FunctionDescriptor fd = function.getFunctionDescriptor(); // Evaluate args Expression[] args = function.getArgs(); Object[] values = null; int start = 0; if (fd.requiresContext()) { values = new Object[args.length+1]; values[0] = context; start = 1; } else { values = new Object[args.length]; } for(int i=0; i < args.length; i++) { values[i+start] = internalEvaluate(args[i]); } if (fd.getPushdown() == PushDown.MUST_PUSHDOWN) { try { return evaluatePushdown(function, values); } catch (Exception e) { throw new TeiidClientException(e); } } if (fd.getProcedure() != null) { try { return evaluateProcedure(function, values); } catch (Exception e) { throw new TeiidClientException(e); } } // Check for special lookup function if(IFunctionLibrary.FunctionName.LOOKUP.equalsIgnoreCase(function.getName())) { String codeTableName = (String) values[0]; String returnElementName = (String) values[1]; String keyElementName = (String) values[2]; /* * Full-version uses the processor to lookup the value which * requires a load more classes that seem unnecessary for * just a validation and 1 function. */ if (codeTableName != null && returnElementName != null && keyElementName != null) return codeTableName + SQLConstants.Tokens.DOT + returnElementName + SQLConstants.Tokens.DOT + keyElementName; else throw new TeiidClientException(); } // Execute function return fd.invokeFunction(values, context, null); } protected Object evaluatePushdown(Function function, Object[] values) throws Exception { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30341, function.getFunctionDescriptor().getFullName())); } private Object evaluate(ScalarSubquery scalarSubquery) throws Exception { Object result = null; ValueIterator valueIter; try { valueIter = evaluateSubquery(scalarSubquery); } catch (Exception e) { throw new TeiidClientException(e); } if(valueIter.hasNext()) { result = valueIter.next(); if(valueIter.hasNext()) { // The subquery should be scalar, but has produced // more than one result value - this is an exception case throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30345, scalarSubquery.getCommand())); } } return result; } /** * @param container * @param tuple * @return * @throws TeiidProcessingException * @throws BlockedException * @throws TeiidComponentException */ protected ValueIterator evaluateSubquery(SubqueryContainer<?> container) throws Exception { throw new UnsupportedOperationException("Subquery evaluation not possible with a base Evaluator"); //$NON-NLS-1$ } protected Object evaluateProcedure(Function function, Object[] values) throws Exception { throw new UnsupportedOperationException("Procedure evaluation not possible with a base Evaluator"); //$NON-NLS-1$ } }