Commit 1986ed0e authored by gmantele's avatar gmantele
Browse files

[ADQL,TAP] Add UDF support in the TAP configuration file.

parent 17f0ec65
Loading
Loading
Loading
Loading
+53 −2
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ package adql.db;
 *                            Astronomisches Rechen Institut (ARI)
 */

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -62,6 +64,7 @@ import adql.query.operand.function.geometry.PolygonFunction;
import adql.query.operand.function.geometry.RegionFunction;
import adql.search.ISearchHandler;
import adql.search.SearchColumnHandler;
import adql.search.SimpleReplaceHandler;
import adql.search.SimpleSearchHandler;

/**
@@ -780,7 +783,7 @@ public class DBChecker implements QueryChecker {
	 * @since 1.3
	 */
	protected void checkUDFs(final ADQLQuery query, final UnresolvedIdentifiersException errors){
		// Search all UDFs:
		// 1. Search all UDFs:
		ISearchHandler sHandler = new SearchUDFHandler();
		sHandler.search(query);

@@ -789,7 +792,7 @@ public class DBChecker implements QueryChecker {
			for(ADQLObject result : sHandler)
				errors.addException(new UnresolvedFunction((UserDefinedFunction)result));
		}
		// Otherwise, try to resolve all of them:
		// 2. Try to resolve all of them:
		else{
			ArrayList<UserDefinedFunction> toResolveLater = new ArrayList<UserDefinedFunction>();
			UserDefinedFunction udf;
@@ -835,6 +838,9 @@ public class DBChecker implements QueryChecker {
				else if (udf instanceof DefaultUDF)
					((DefaultUDF)udf).setDefinition(allowedUdfs[match]);
			}

			// 3. Replace all the resolved DefaultUDF by an instance of the class associated with the set signature:
			(new ReplaceDefaultUDFHandler(errors)).searchAndReplace(query);
		}
	}

@@ -1305,6 +1311,51 @@ public class DBChecker implements QueryChecker {
		}
	}

	/**
	 * <p>Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is set by their corresponding {@link UserDefinedFunction} class.</p>
	 * 
	 * <p><i><b>Important note:</b>
	 * 	If the replacer can not be created using the class returned by {@link FunctionDef#getUDFClass()}, no replacement is performed.
	 * </i></p>
	 * 
	 * @author Gr&eacute;gory Mantelet (ARI)
	 * @version 1.3 (02/2015)
	 * @since 1.3
	 */
	private static class ReplaceDefaultUDFHandler extends SimpleReplaceHandler {
		private final UnresolvedIdentifiersException errors;

		public ReplaceDefaultUDFHandler(final UnresolvedIdentifiersException errorsContainer){
			errors = errorsContainer;
		}

		@Override
		protected boolean match(ADQLObject obj){
			return (obj.getClass().getName().equals(DefaultUDF.class.getName())) && (((DefaultUDF)obj).getDefinition() != null) && (((DefaultUDF)obj).getDefinition().getUDFClass() != null);
			/* Note: detection of DefaultUDF is done on the exact class name rather than using "instanceof" in order to have only direct instances of DefaultUDF,
			 * and not extensions of it. Indeed, DefaultUDFs are generally created automatically by the ADQLQueryFactory ; so, extensions of it can only be custom
			 * UserDefinedFunctions. */
		}

		@Override
		protected ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException{
			try{
				// get the associated UDF class:
				Class<? extends UserDefinedFunction> udfClass = ((DefaultUDF)objToReplace).getDefinition().getUDFClass();
				// get the constructor with a single parameter of type ADQLOperand[]:
				Constructor<? extends UserDefinedFunction> constructor = udfClass.getConstructor(ADQLOperand[].class);
				// create a new instance of this UDF class with the operands stored in the object to replace:
				return constructor.newInstance((Object)(((DefaultUDF)objToReplace).getParameters())); /* note: without this class, each item of the given array will be considered as a single parameter. */
			}catch(Exception ex){
				// IF NO INSTANCE CAN BE CREATED...
				// ...keep the error for further report:
				errors.addException(new UnresolvedFunction("Impossible to represent the function \"" + ((DefaultUDF)objToReplace).getName() + "\": the following error occured while creating this representation: \"" + ((ex instanceof InvocationTargetException) ? "[" + ex.getCause().getClass().getSimpleName() + "] " + ex.getCause().getMessage() : ex.getMessage()) + "\"", (DefaultUDF)objToReplace));
				// ...keep the same object (i.e. no replacement):
				return objToReplace;
			}
		}
	}

	/**
	 * Let searching geometrical functions.
	 * 
+67 −2
Original line number Diff line number Diff line
@@ -16,15 +16,19 @@ package adql.db;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2014 - Astronomisches Rechen Institut (ARI)
 * Copyright 2015 - Astronomisches Rechen Institut (ARI)
 */

import java.lang.reflect.Constructor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import adql.db.DBType.DBDatatype;
import adql.parser.ParseException;
import adql.query.operand.ADQLOperand;
import adql.query.operand.function.ADQLFunction;
import adql.query.operand.function.DefaultUDF;
import adql.query.operand.function.UserDefinedFunction;

/**
 * <p>Definition of any function that could be used in ADQL queries.</p>
@@ -45,7 +49,7 @@ import adql.query.operand.function.ADQLFunction;
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (ARI)
 * @version 1.3 (10/2014)
 * @version 1.3 (02/2015)
 * 
 * @since 1.3
 */
@@ -105,6 +109,13 @@ public class FunctionDef implements Comparable<FunctionDef> {
	 * <pre>{fctName}([xxx, ...])</pre> */
	private final String compareForm;

	/**
	 * <p>Class of the {@link UserDefinedFunction} which must represent the UDF defined by this {@link FunctionDef} in the ADQL tree.</p>
	 * <p>This class MUST have a constructor with a single parameter of type {@link ADQLOperand}[].</p>
	 * <p>If this {@link FunctionDef} is defining an ordinary ADQL function, this attribute must be NULL. It is used only for user defined functions.</p> 
	 */
	private Class<? extends UserDefinedFunction> udfClass = null;

	/**
	 * <p>Definition of a function parameter.</p>
	 * 
@@ -258,6 +269,60 @@ public class FunctionDef implements Comparable<FunctionDef> {
			return params[indParam];
	}

	/**
	 * <p>Get the class of the {@link UserDefinedFunction} able to represent the function defined here in an ADQL tree.</p>
	 * 
	 * <p><i>Note:
	 * 	This getter should return always NULL if the function defined here is not a user defined function.
	 * 	<br/>
	 * 	However, if this {@link FunctionDef} is defining a user defined function and this function returns NULL,
	 * 	the library will create on the fly a {@link DefaultUDF} corresponding to this definition when needed.
	 * 	Indeed this UDF class is useful only if the translation from ADQL (to SQL for instance) of the defined
	 * 	function has a different signature (e.g. a different name) in the target language (e.g. SQL).
	 * </i></p>
	 * 
	 * @return	The corresponding {@link UserDefinedFunction}. <i>MAY BE NULL</i>
	 */
	public final Class<? extends UserDefinedFunction> getUDFClass(){
		return udfClass;
	}

	/**
	 * <p>Set the class of the {@link UserDefinedFunction} able to represent the function defined here in an ADQL tree.</p>
	 * 
	 * <p><i>Note:
	 * 	If this {@link FunctionDef} defines an ordinary ADQL function - and not a user defined function - no class should be set here.
	 * 	<br/>
	 * 	However, if it defines a user defined function, there is no obligation to set a UDF class. It is useful only if the translation
	 * 	from ADQL (to SQL for instance) of the function has a different signature (e.g. a different name) in the target language (e.g. SQL).
	 * 	If the signature is the same, there is no need to set a UDF class ; a {@link DefaultUDF} will be created on the fly by the library
	 * 	when needed if it turns out that no UDF class is set.
	 * </i></p>
	 * 
	 * @param udfClass	Class to use to represent in an ADQL tree the User Defined Function defined in this {@link FunctionDef}.
	 * 
	 * @throws IllegalArgumentException	If the given class does not provide any constructor with a single parameter of type ADQLOperand[].
	 */
	public final < T extends UserDefinedFunction > void setUDFClass(final Class<T> udfClass) throws IllegalArgumentException{
		try{

			// Ensure that, if a class is provided, it contains a constructor with a single parameter of type ADQLOperand[]:
			if (udfClass != null){
				Constructor<T> constructor = udfClass.getConstructor(ADQLOperand[].class);
				if (constructor == null)
					throw new IllegalArgumentException("The given class (" + udfClass.getName() + ") does not provide any constructor with a single parameter of type ADQLOperand[]!");
			}

			// Set the new UDF class:
			this.udfClass = udfClass;

		}catch(SecurityException e){
			throw new IllegalArgumentException("A security problem occurred while trying to get constructor from the class " + udfClass.getName() + ": " + e.getMessage());
		}catch(NoSuchMethodException e){
			throw new IllegalArgumentException("The given class (" + udfClass.getName() + ") does not provide any constructor with a single parameter of type ADQLOperand[]!");
		}
	}

	/**
	 * <p>Let parsing the serialized form of a function definition.</p>
	 * 
+21 −4
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package adql.query.operand.function;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

@@ -25,12 +25,14 @@ import adql.query.ADQLList;
import adql.query.ADQLObject;
import adql.query.ClauseADQL;
import adql.query.operand.ADQLOperand;
import adql.translator.ADQLTranslator;
import adql.translator.TranslationException;

/**
 * It represents any function which is not managed by ADQL.
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 1.3 (10/2014)
 * @version 1.3 (02/2015)
 */
public final class DefaultUDF extends UserDefinedFunction {

@@ -48,7 +50,7 @@ public final class DefaultUDF extends UserDefinedFunction {
	 * Creates a user function.
	 * @param params	Parameters of the function.
	 */
	public DefaultUDF(final String name, ADQLOperand[] params) throws NullPointerException{
	public DefaultUDF(final String name, final ADQLOperand[] params) throws NullPointerException{
		functionName = name;
		parameters = new ClauseADQL<ADQLOperand>();
		if (params != null){
@@ -64,7 +66,7 @@ public final class DefaultUDF extends UserDefinedFunction {
	 * @throws Exception	If there is an error during the copy.
	 */
	@SuppressWarnings("unchecked")
	public DefaultUDF(DefaultUDF toCopy) throws Exception{
	public DefaultUDF(final DefaultUDF toCopy) throws Exception{
		functionName = toCopy.functionName;
		parameters = (ADQLList<ADQLOperand>)(toCopy.parameters.getCopy());
	}
@@ -91,6 +93,8 @@ public final class DefaultUDF extends UserDefinedFunction {
	 * @param def	The definition applying to this parsed UDF, or NULL if none has been found.
	 * 
	 * @throws IllegalArgumentException	If the name in the given definition does not match the name of this parsed function.
	 * 
	 * @since 1.3
	 */
	public final void setDefinition(final FunctionDef def) throws IllegalArgumentException{
		if (def != null && (def.name == null || !functionName.equalsIgnoreCase(def.name)))
@@ -155,4 +159,17 @@ public final class DefaultUDF extends UserDefinedFunction {
		return parameters.set(index, replacer);
	}

	@Override
	public String translate(final ADQLTranslator caller) throws TranslationException{
		StringBuffer sql = new StringBuffer(functionName);
		sql.append('(');
		for(int i = 0; i < parameters.size(); i++){
			if (i > 0)
				sql.append(',').append(' ');
			sql.append(caller.translate(parameters.get(i)));
		}
		sql.append(')');
		return sql.toString();
	}

}
+38 −4
Original line number Diff line number Diff line
package adql.query.operand.function;

import adql.query.operand.UnknownType;

/*
 * This file is part of ADQLLibrary.
 * 
@@ -18,15 +16,19 @@ import adql.query.operand.UnknownType;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

import adql.query.operand.UnknownType;
import adql.translator.ADQLTranslator;
import adql.translator.TranslationException;

/**
 * Function defined by the user (i.e. PSQL functions).
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 1.3 (10/2014)
 * @version 1.3 (02/2015)
 * 
 * @see DefaultUDF
 */
@@ -46,4 +48,36 @@ public abstract class UserDefinedFunction extends ADQLFunction implements Unknow
		expectedType = c;
	}

	/**
	 * <p>Translate this User Defined Function into the language supported by the given translator.</p>
	 * 
	 * <p><b>VERY IMPORTANT:</b> This function <b>MUST NOT use</b> {@link ADQLTranslator#translate(UserDefinedFunction))} to translate itself.
	 * The given {@link ADQLTranslator} <b>must be used ONLY</b> to translate UDF's operands.</p>
	 * 
	 * <p>Implementation example (extract of {@link DefaultUDF#translate(ADQLTranslator)}):</p>
	 * <pre>
	 * public String translate(final ADQLTranslator caller) throws TranslationException{
	 * 	StringBuffer sql = new StringBuffer(functionName);
	 * 	sql.append('(');
	 * 	for(int i = 0; i < parameters.size(); i++){
	 *		if (i > 0)
	 *			sql.append(',').append(' ');
	 * 		sql.append(caller.translate(parameters.get(i)));
	 *	}
	 *	sql.append(')');
	 *	return sql.toString();
	 * }
	 * </pre>
	 * 
	 * 
	 * @param caller	Translator to use in order to translate <b>ONLY</b> function parameters.
	 * 
	 * @return	The translation of this UDF into the language supported by the given translator.
	 * 
	 * @throws TranslationException	If one of the parameters can not be translated.
	 * 
	 * @since 1.3
	 */
	public abstract String translate(final ADQLTranslator caller) throws TranslationException;

}
+3 −3
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package adql.translator;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2014 - Astronomisches Rechen Institut (ARI)
 * Copyright 2015 - Astronomisches Rechen Institut (ARI)
 */

import java.util.ArrayList;
@@ -167,7 +167,7 @@ import adql.query.operand.function.geometry.RegionFunction;
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (ARI)
 * @version 2.0 (02/2015)
 * @version 1.3 (02/2015)
 * @since 1.3
 * 
 * @see PostgreSQLTranslator
@@ -791,7 +791,7 @@ public abstract class JDBCTranslator implements ADQLTranslator {

	@Override
	public String translate(UserDefinedFunction fct) throws TranslationException{
		return getDefaultADQLFunction(fct);
		return fct.translate(this);
	}

	/* *********************************** */
Loading