/* * 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.metadata; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.teiid.designer.annotation.Since; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version; import org.teiid.query.function.metadata.FunctionCategoryConstants; import org.teiid.query.function.metadata.FunctionMetadataValidator; /** * <p>This class represents information about a particular function signature. * Function signatures are unique with respect to their name, # of arguments, * and type of arguments. Return type and argument names are not uniqueness * factors. This class makes no attempt to validate the data put into it, * particularly with respect to null values. The * {@link FunctionMetadataValidator} can be used to validate this object.</p> * * Attributes: * <UL> * <LI>name - Name of the function</LI> * <LI>description - Description of the function</LI> * <LI>category - Function category containing this function</LI> * <LI>pushdown - Determine whether this function can, cannot, or must be pushed down to a source</LI> * <LI>invocationClass - Class containing method implementing this function</LI> * <LI>invocationMethod - Method implementing this function</LI> * <LI>inputParameters - 0 or more input parameters</LI> * <LI>outputParameter - 1 output parameter</LI> * <LI>determinism - specifies whether the function is deterministic or not. Various levels are provided * <LI>nullOnNull - Specifies whether the function is called if any of the input arguments is null. The result is the null value. * </UL> * * @see FunctionParameter */ public class FunctionMethod extends AbstractMetadataRecord { @Since(Version.TEIID_8_9) public static final String SYSTEM_NAME = AbstractMetadataRecord.RELATIONAL_URI + "system-name"; //$NON-NLS-1$ private static final long serialVersionUID = -8039086494296455152L; private static final String NOT_ALLOWED = "NOT_ALLOWED"; //$NON-NLS-1$ private static final String ALLOWED = "ALLOWED"; //$NON-NLS-1$ private static final String REQUIRED = "REQUIRED"; //$NON-NLS-1$ /** * Function Pushdown * CAN_PUSHDOWN = If the source supports the function, then it will be pushed down. Must supply the Java impl * CANNOT_PUSHDOWN = It will not be pushed down, evaluated in Teiid. Must supply the Java impl * MUST_PUSHDOWN = Function must be pushed to source, no need to supply Java impl. * SYNTHETIC = system functions that will be rewritten */ public enum PushDown {CAN_PUSHDOWN, CANNOT_PUSHDOWN, MUST_PUSHDOWN, SYNTHETIC}; /** * DETERMINISTIC -> normal deterministic functions * vdb -> lookup (however lookup values can be flushed at any time), current_database * session -> env, user * command -> command payload * never -> rand, etc. * * Anything at a session level and above is treated as deterministic. * This is not quite correct for lookup or env. Only in extremely rare * circumstances would that be a problem. * * For now the commandPayload function is treated as a special case, like lookup, in * that it is considered deterministic, but will be delayed in its evaluation until * processing time. */ public enum Determinism{ NONDETERMINISTIC, COMMAND_DETERMINISTIC, SESSION_DETERMINISTIC, USER_DETERMINISTIC, VDB_DETERMINISTIC, DETERMINISTIC; } private String description; private String category; private PushDown pushdown = PushDown.CAN_PUSHDOWN; private String invocationClass; private String invocationMethod; private boolean nullOnNull; private AggregateAttributes aggregateAttributes; private transient Method method; private Determinism determinism = Determinism.DETERMINISTIC; protected List<FunctionParameter> inParameters = new ArrayList<FunctionParameter>(2); private FunctionParameter outputParameter; private Schema parent; public FunctionMethod() { } public FunctionMethod(String name, String description, String category, FunctionParameter[] inputParams, FunctionParameter outputParam) { this(name, description, category, PushDown.MUST_PUSHDOWN, null, null, inputParams!=null?Arrays.asList(inputParams):null, outputParam, true, Determinism.DETERMINISTIC); } public FunctionMethod(String name, String description, String category, PushDown pushdown, String invocationClass, String invocationMethod, List<FunctionParameter> inputParams, FunctionParameter outputParam, boolean nullOnNull, Determinism deterministic) { setName(name); setDescription(description); setCategory(category); setPushdown(pushdown); setInvocationClass(invocationClass); setInvocationMethod(invocationMethod); setInputParameters(inputParams); setOutputParameter(outputParam); setNullOnNull(nullOnNull); setDeterminism(deterministic); } public FunctionMethod(String name, String description, String category, String invocationClass, String invocationMethod, FunctionParameter[] inputParams, FunctionParameter outputParam) { this(name, description, category, PushDown.CAN_PUSHDOWN, invocationClass, invocationMethod, inputParams!=null?Arrays.asList(inputParams):null, outputParam, true, Determinism.DETERMINISTIC); } /** * Get description of method * @return Description */ public String getDescription() { return this.description; } /** * Set description of method * @param description Description */ public void setDescription(String description) { this.description = description; } /** * Get category of method * @return Category * @see FunctionCategoryConstants */ public String getCategory() { return this.category; } /** * Set category of method * @param category Category * @see FunctionCategoryConstants */ public void setCategory(String category) { this.category = category; } /** * Get pushdown property of method * @return One of the FunctionMethod constants for pushdown */ public PushDown getPushdown() { return pushdown; } /** * Set pushdown property of method * @param pushdown One of the FunctionMethod constants for pushdown */ public void setPushdown(PushDown pushdown) { this.pushdown = pushdown; } public void setPushDown(String pushdown) { if (pushdown != null) { if (pushdown.equals(REQUIRED)) { this.pushdown = PushDown.MUST_PUSHDOWN; } else if (pushdown.equals(ALLOWED)) { this.pushdown = PushDown.CAN_PUSHDOWN; } else if (pushdown.equals(NOT_ALLOWED)) { this.pushdown = PushDown.CANNOT_PUSHDOWN; } } else { this.pushdown = PushDown.CAN_PUSHDOWN; } } /** * Get invocation class name * @return Invocation class name */ public String getInvocationClass() { return this.invocationClass; } /** * Set invocation class name * @param invocationClass Invocation class name */ public void setInvocationClass(String invocationClass) { this.invocationClass = invocationClass; } /** * Get invocation method name * @return Invocation method name */ public String getInvocationMethod() { return this.invocationMethod; } /** * Set invocation method name * @param invocationMethod Invocation method name */ public void setInvocationMethod(String invocationMethod) { this.invocationMethod = invocationMethod; } /** * Get a count of the input parameters. * @return Number of input parameters */ public int getInputParameterCount() { if(this.inParameters == null) { return 0; } return this.inParameters.size(); } /** * Get input parameters * @return Array of input parameters, may be null if 0 parameters */ public List<FunctionParameter> getInputParameters() { return this.inParameters; } /** * Set input parameters. * @param params Input parameters */ public void setInputParameters(List<FunctionParameter> params) { this.inParameters.clear(); if (params != null) { this.inParameters.addAll(params); } } /** * Get ouput/return parameter. * @return Output parameter or return argument */ public FunctionParameter getOutputParameter() { return this.outputParameter; } /** * Set ouput/return parameter. * @param param Output Parameter */ public void setOutputParameter(FunctionParameter param) { if (param != null) { param.setName(FunctionParameter.OUTPUT_PARAMETER_NAME); } this.outputParameter = param; } /** * Get hash code for this object. The hash code is based on the name * and input parameters. <B>WARNING: Changing the name or input parameters * will change the hash code.</B> If this occurs after the object has been * placed in a HashSet or HashMap, the object will be lost!!!! In that * case, the object must be added to the hashed collection again. * @return Hash code, based on name and input parameters */ @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((super.getName() == null) ? 0 : super.getName().hashCode()); result = prime * result + ((inParameters == null) ? 0 : inParameters.hashCode()); return result; } /** * Compare other object for equality. This object is equal to another * FunctionMethod if 1) Name of function matches (case-insensitive), * 2) number of input parameters matches and 3) types of input parameters * match. * @return True if object equals this object according to conditions */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; FunctionMethod other = (FunctionMethod)obj; // Compare # of parameters - do this first as it is much faster than name compare if(getInputParameterCount() != other.getInputParameterCount()) return false; if (this.parent == null) { if (other.parent != null) return false; } else if (!this.parent.equals(other.parent)) return false; if (this.getName() == null) { if (other.getName() != null) return false; } else if (!this.getName().equals(other.getName())) return false; // Compare types of parameters List<FunctionParameter> thisInputs = this.getInputParameters(); if(thisInputs != null && thisInputs.size() > 0) { // If thisInputs is not null and >0 and other parameter // count matched this parameter count, otherInputs MUST be // non null to have more than one parameter - so we don't // need to check it here. List<FunctionParameter> otherInputs = other.getInputParameters(); for(int i=0; i<thisInputs.size(); i++) { if (thisInputs.get(i) == null) { if (otherInputs.get(i) != null) return false; } else if (!thisInputs.get(i).equals(otherInputs.get(i))) return false; } } return true; } /** * Return string version for debugging purposes * @return String representation of function method */ public String toString() { StringBuffer str = new StringBuffer(); if(getName() != null) { str.append(getName()); } else { str.append("<unknown>"); //$NON-NLS-1$ } // Print parameters str.append("("); //$NON-NLS-1$ if(inParameters != null) { for(int i=0; i<inParameters.size(); i++) { if(inParameters.get(i) != null) { str.append(inParameters.get(i).toString()); } else { str.append("<unknown>"); //$NON-NLS-1$ } if(i < (inParameters.size()-1)) { str.append(", "); //$NON-NLS-1$ } } } str.append(") : "); //$NON-NLS-1$ // Print return type if(outputParameter != null) { str.append(outputParameter.toString()); } else { str.append("<unknown>"); //$NON-NLS-1$ } return str.toString(); } /** * Returns true if the function returns null on any null input */ public boolean isNullOnNull() { return this.nullOnNull; } public void setNullOnNull(boolean nullOnNull) { this.nullOnNull = nullOnNull; } public Determinism getDeterminism() { return this.determinism; } public void setDeterministicBoolean(boolean deterministic) { this.determinism = deterministic ? Determinism.DETERMINISTIC : Determinism.NONDETERMINISTIC; } public void setDeterminism(Determinism determinism) { this.determinism = determinism; } public boolean isVarArgs() { if (this.inParameters != null && this.inParameters.size() > 0) { return inParameters.get(inParameters.size() - 1).isVarArg(); } return false; } /** * * @param varargs * @return true if the value was successfully set. */ public boolean setVarArgs(boolean varargs) { if (this.inParameters != null && this.inParameters.size() > 0) { inParameters.get(inParameters.size() - 1).setVarArg(varargs); return true; } return false; } public void setParent(Schema parent) { this.parent = parent; } @Override public Schema getParent() { return parent; } /** * Gets the {@link AggregateAttributes} for this function if it * represents an aggregate function. Must be null for non-aggregates. * @return */ public AggregateAttributes getAggregateAttributes() { return aggregateAttributes; } public void setAggregateAttributes(AggregateAttributes aggregateAttributes) { this.aggregateAttributes = aggregateAttributes; } public static void convertExtensionMetadata(Procedure procedureRecord, FunctionMethod function) { String deterministic = procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "deterministic", true); //$NON-NLS-1$ boolean nullOnNull = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "null-on-null", true)); //$NON-NLS-1$ String varargs = procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "varargs", true); //$NON-NLS-1$ String javaClass = procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "java-class", true); //$NON-NLS-1$ String javaMethod = procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "java-method", true); //$NON-NLS-1$ if (function.getInvocationClass() == null) { function.setInvocationClass(javaClass); } if (function.getInvocationMethod() == null) { function.setInvocationMethod(javaMethod); } if (!procedureRecord.getParameters().isEmpty()) { function.setProperties(procedureRecord.getProperties()); } boolean aggregate = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "aggregate", true)); //$NON-NLS-1$ if (deterministic != null) { function.setDeterminism(Boolean.valueOf(deterministic) ? Determinism.DETERMINISTIC : Determinism.NONDETERMINISTIC); } function.setNullOnNull(nullOnNull); if (varargs != null && !function.getInputParameters().isEmpty()) { function.getInputParameters().get(function.getInputParameterCount() - 1).setVarArg(Boolean.valueOf(varargs)); } if (aggregate) { boolean analytic = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "analytic", true)); //$NON-NLS-1$ boolean allowsOrderBy = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "allows-orderby", true)); //$NON-NLS-1$ boolean usesDistinctRows = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "uses-distinct-rows", true)); //$NON-NLS-1$ boolean allowsDistinct = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "allows-distinct", true)); //$NON-NLS-1$ boolean decomposable = Boolean.valueOf(procedureRecord.getProperty(AbstractMetadataRecord.RELATIONAL_URI + "decomposable", true)); //$NON-NLS-1$ AggregateAttributes aa = new AggregateAttributes(); aa.setAnalytic(analytic); aa.setAllowsOrderBy(allowsOrderBy); aa.setUsesDistinctRows(usesDistinctRows); aa.setAllowsDistinct(allowsDistinct); aa.setDecomposable(decomposable); function.setAggregateAttributes(aa); } } public static FunctionMethod createFunctionMethod(ITeiidServerVersion version, String name, String description, String category, String returnType, String... paramTypes) { FunctionParameter[] params = new FunctionParameter[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { params[i] = new FunctionParameter(version, "param" + (i+1), paramTypes[i]); //$NON-NLS-1$ } FunctionMethod method = new FunctionMethod(name, description, category, params, new FunctionParameter(version, "result", returnType)); //$NON-NLS-1$ method.setNameInSource(name); return method; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } }