/* * 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.optimizer.relational.rules; import java.util.Iterator; import java.util.List; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.core.TeiidComponentException; import org.teiid.core.types.DataTypeManager; import org.teiid.language.SortSpecification.NullOrdering; import org.teiid.metadata.FunctionMethod; import org.teiid.metadata.FunctionMethod.PushDown; import org.teiid.metadata.Schema; import org.teiid.query.function.FunctionLibrary; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.optimizer.capabilities.CapabilitiesFinder; import org.teiid.query.optimizer.capabilities.SourceCapabilities; import org.teiid.query.optimizer.capabilities.SourceCapabilities.Capability; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.lang.JoinType; import org.teiid.query.sql.lang.OrderByItem; import org.teiid.query.sql.lang.SetQuery.Operation; import org.teiid.query.sql.symbol.AggregateSymbol; import org.teiid.query.sql.symbol.AggregateSymbol.Type; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.util.SymbolMap; import org.teiid.translator.ExecutionFactory.NullOrder; import org.teiid.translator.ExecutionFactory.SupportedJoinCriteria; import org.teiid.translator.SourceSystemFunctions; /** */ public class CapabilitiesUtil { /** * Can't construct - just a utilities class */ private CapabilitiesUtil() { } static boolean supportsInlineView(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_FROM_INLINE_VIEWS, modelID, metadata, capFinder); } public static boolean supportsSelfJoins(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); return caps.supportsCapability(Capability.QUERY_FROM_JOIN_SELFJOIN) && caps.supportsCapability(Capability.QUERY_FROM_GROUP_ALIAS); } public static boolean supportsGroupAliases(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_FROM_GROUP_ALIAS, modelID, metadata, capFinder); } public static boolean supportsJoin(Object modelID, JoinType joinType, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); if (!joinType.isOuter()) { return caps.supportsCapability(Capability.QUERY_FROM_JOIN_INNER) || caps.supportsCapability(Capability.QUERY_FROM_JOIN_OUTER); } if(! caps.supportsCapability(Capability.QUERY_FROM_JOIN_OUTER)) { return false; } return !joinType.equals(JoinType.JOIN_FULL_OUTER) || caps.supportsCapability(Capability.QUERY_FROM_JOIN_OUTER_FULL); } public static boolean supportsAggregates(List groupCols, Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); if (groupCols != null && !groupCols.isEmpty()) { if (!caps.supportsCapability(Capability.QUERY_GROUP_BY)) { return false; } boolean supportsFunctionsInGroupBy = caps.supportsCapability(Capability.QUERY_FUNCTIONS_IN_GROUP_BY); boolean supportsInlineView = caps.supportsCapability(Capability.QUERY_FROM_INLINE_VIEWS); // Also verify that if there is a function that we can support pushdown of functions in group by Iterator colIter = groupCols.iterator(); while(colIter.hasNext()) { Expression col = (Expression) colIter.next(); if(!(col instanceof ElementSymbol) && !supportsFunctionsInGroupBy && !supportsInlineView) { // Function in GROUP BY can't be pushed return false; } } } return true; } public static boolean supportsAggregateFunction(Object modelID, AggregateSymbol aggregate, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); // Check particular function Type func = aggregate.getAggregateFunction(); switch (func) { case COUNT: if(aggregate.getArgs().length == 0) { if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_COUNT_STAR)) { return false; } } else { if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_COUNT)) { return false; } } break; case SUM: if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_SUM)) { return false; } break; case AVG: if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_AVG)) { return false; } break; case MIN: if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_MIN)) { return false; } break; case MAX: if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_MAX)) { return false; } break; case ARRAY_AGG: if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_ARRAY)) { return false; } break; case STRING_AGG: if(! caps.supportsCapability(Capability.QUERY_AGGREGATES_STRING)) { return false; } break; case RANK: case DENSE_RANK: case ROW_NUMBER: case FIRST_VALUE: case LAST_VALUE: case LEAD: case LAG: if (!caps.supportsCapability(Capability.ELEMENTARY_OLAP)) { return false; } break; case USER_DEFINED: if (!supportsScalarFunction(modelID, aggregate, metadata, capFinder)) { return false; } break; default: if (aggregate.isEnhancedNumeric()) { if (!caps.supportsCapability(Capability.QUERY_AGGREGATES_ENHANCED_NUMERIC)) { return false; } } else { return false; } break; } // Check DISTINCT if necessary if(aggregate.isDistinct() && ! caps.supportsCapability(Capability.QUERY_AGGREGATES_DISTINCT)) { return false; } if (aggregate.getCondition() != null && !caps.supportsCapability(Capability.ADVANCED_OLAP)) { return false; } // Passed all the checks! return true; } public static boolean supportsScalarFunction(Object modelID, Function function, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { FunctionMethod method = function.getFunctionDescriptor().getMethod(); if (metadata.isVirtualModel(modelID) || method.getPushdown() == PushDown.CANNOT_PUSHDOWN){ return false; } SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); //capabilities check is only valid for non-schema scoped functions //technically the other functions are scoped to SYS or their function model, but that's //not formally part of their metadata yet Schema schema = method.getParent(); //TODO: this call should be functionDescriptor.getFullName - but legacy function models are parsed without setting the parent model as the schema String fullName = method.getFullName(); if (schema == null || !schema.isPhysical()) { // Find capabilities if (!caps.supportsFunction(fullName)) { if(SourceSystemFunctions.CONCAT2.equalsIgnoreCase(fullName)) { //special handling for delayed rewrite of concat2 return (schema == null && caps.supportsFunction(SourceSystemFunctions.CONCAT) && caps.supportsFunction(SourceSystemFunctions.IFNULL) && caps.supportsCapability(Capability.QUERY_SEARCHED_CASE)); } else if(SourceSystemFunctions.FROM_UNIXTIME.equalsIgnoreCase(fullName)) { return (schema == null && caps.supportsFunction(SourceSystemFunctions.TIMESTAMPADD)); } else { return false ; } } if (FunctionLibrary.isConvert(function)) { Class<?> fromType = function.getArg(0).getType(); Class<?> targetType = function.getType(); if (fromType == targetType) { return true; //this should be removed in rewrite } return caps.supportsConvert(DataTypeManager.getTypeCode(fromType), DataTypeManager.getTypeCode(targetType)); } } else if (!isSameConnector(modelID, schema, metadata, capFinder)) { return caps.supportsFunction(fullName); } return true; } public static boolean supportsSelectDistinct(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_SELECT_DISTINCT, modelID, metadata, capFinder); } public static boolean supportsSelectExpression(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_SELECT_EXPRESSION, modelID, metadata, capFinder); } public static boolean supportsOrderBy(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_ORDERBY, modelID, metadata, capFinder); } public static boolean supportsSetOp(Object modelID, Operation setOp, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); switch (setOp) { case EXCEPT: return caps.supportsCapability(Capability.QUERY_EXCEPT); case INTERSECT: return caps.supportsCapability(Capability.QUERY_INTERSECT); case UNION: return caps.supportsCapability(Capability.QUERY_UNION); } return false; } public static boolean supportsSetQueryOrderBy(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_SET_ORDER_BY, modelID, metadata, capFinder); } public static boolean supportsCaseExpression(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_CASE, modelID, metadata, capFinder); } public static boolean supportsSearchedCaseExpression(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_SEARCHED_CASE, modelID, metadata, capFinder); } public static int getMaxInCriteriaSize(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return getIntProperty(Capability.MAX_IN_CRITERIA_SIZE, modelID, metadata, capFinder); } public static Object getProperty(Capability cap, Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return null; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); return caps.getSourceProperty(cap); } /** * Values are expected to be non-negative except for unknown/invalid = -1 * @param cap * @param modelID * @param metadata * @param capFinder * @return * @throws QueryMetadataException * @throws TeiidComponentException */ public static int getIntProperty(Capability cap, Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { Object i = getProperty(cap, modelID, metadata, capFinder); int value = -1; if(i != null) { value = ((Integer)i).intValue(); } // Check for invalid values and send back code for UNKNOWN if(value <= 0) { value = -1; } return value; } public static int getMaxDependentPredicates(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return getIntProperty(Capability.MAX_DEPENDENT_PREDICATES, modelID, metadata, capFinder); } public static int getMaxFromGroups(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return getIntProperty(Capability.MAX_QUERY_FROM_GROUPS, modelID, metadata, capFinder); } public static SupportedJoinCriteria getSupportedJoinCriteria(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return SupportedJoinCriteria.ANY; } SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); SupportedJoinCriteria crits = (SupportedJoinCriteria)caps.getSourceProperty(Capability.JOIN_CRITERIA_ALLOWED); if (crits == null) { return SupportedJoinCriteria.ANY; } return crits; } public static NullOrder getDefaultNullOrder(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return NullOrder.UNKNOWN; } SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); NullOrder order = (NullOrder)caps.getSourceProperty(Capability.QUERY_ORDERBY_DEFAULT_NULL_ORDER); if (order == null) { return NullOrder.UNKNOWN; } return order; } public static boolean supportsRowLimit(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.ROW_LIMIT, modelID, metadata, capFinder); } public static boolean supportsRowOffset(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.ROW_OFFSET, modelID, metadata, capFinder); } public static boolean isSameConnector(Object modelID, Object modelID1, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (modelID == null || modelID1 == null || metadata.isVirtualModel(modelID) || metadata.isVirtualModel(modelID1)){ return false; } if (modelID.equals(modelID1)) { return true; } if (capFinder == null) { return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); SourceCapabilities caps1 = getCapabilities(modelID1, metadata, capFinder); Object connectorID = caps.getSourceProperty(Capability.CONNECTOR_ID); return connectorID != null && connectorID.equals(caps1.getSourceProperty(Capability.CONNECTOR_ID)); } static SourceCapabilities getCapabilities(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { // Find capabilities String modelName = metadata.getFullName(modelID); return capFinder.findCapabilities(modelName); } public static boolean requiresCriteria(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.REQUIRES_CRITERIA, modelID, metadata, capFinder); } public static boolean useAnsiJoin(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { return supports(Capability.QUERY_FROM_ANSI_JOIN, modelID, metadata, capFinder); } public static boolean supports(Capability cap, Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException { if (metadata.isVirtualModel(modelID)){ return false; } // Find capabilities SourceCapabilities caps = getCapabilities(modelID, metadata, capFinder); return caps.supportsCapability(cap); } /** * Validate that the elements are searchable and can be used in a criteria against this source. * TODO: this check is too general and not type based */ static boolean checkElementsAreSearchable(List<? extends LanguageObject> objs, QueryMetadataInterface metadata, int searchableType) throws QueryMetadataException, TeiidComponentException { if (objs != null) { for (LanguageObject lo : objs) { if (lo instanceof OrderByItem) { lo = ((OrderByItem)lo).getSymbol(); } if (!(lo instanceof Expression)) { continue; } lo = SymbolMap.getExpression((Expression)lo); if (!(lo instanceof ElementSymbol)) { continue; } if (!metadata.elementSupports(((ElementSymbol)lo).getMetadataID(), searchableType)) { return false; } } } return true; } static boolean supportsNullOrdering(QueryMetadataInterface metadata, CapabilitiesFinder capFinder, Object modelID, OrderByItem symbol) throws QueryMetadataException, TeiidComponentException { boolean supportsNullOrdering = CapabilitiesUtil.supports(Capability.QUERY_ORDERBY_NULL_ORDERING, modelID, metadata, capFinder); NullOrder defaultNullOrder = CapabilitiesUtil.getDefaultNullOrder(modelID, metadata, capFinder); if (symbol.getNullOrdering() != null && !supportsNullOrdering) { if (symbol.getNullOrdering() == NullOrdering.FIRST) { if (defaultNullOrder != NullOrder.FIRST && !(symbol.isAscending() && defaultNullOrder == NullOrder.LOW) && !(!symbol.isAscending() && defaultNullOrder == NullOrder.HIGH)) { return false; } } else if (defaultNullOrder != NullOrder.LAST && !(symbol.isAscending() && defaultNullOrder == NullOrder.HIGH) && !(!symbol.isAscending() && defaultNullOrder == NullOrder.LOW)) { return false; } } return true; } }