From 3509775af71e9c1f57fb9f7aaab371497384eb9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gr=C3=A9gory=20Mantelet?=
 <gregory.mantelet@astro.unistra.fr>
Date: Thu, 8 Aug 2019 14:00:28 +0200
Subject: [PATCH] [ADQL] Check the UDF name while creating a FunctionDef and
 so, also while creating the corresponding LanguageFeature. Now, FunctionDef
 throws a ParseException when the UDF name is invalid.

---
 src/adql/db/FunctionDef.java                  | 177 +++++++++-
 src/adql/parser/ADQLParser.java               |   2 +
 src/adql/parser/ADQLParser200.java            | 104 +++---
 src/adql/parser/ADQLParser201.java            | 120 ++++---
 src/adql/parser/adqlGrammar200.jj             |  17 +
 src/adql/parser/adqlGrammar201.jj             |  17 +
 .../query/operand/function/DefaultUDF.java    |  19 +-
 test/adql/db/TestDBChecker.java               |  96 ++++--
 test/adql/db/TestFunctionDef.java             | 318 +++++++++++++-----
 test/adql/parser/feature/TestFeatureSet.java  |  27 +-
 test/adql/query/TestADQLQuery.java            |  47 +--
 11 files changed, 684 insertions(+), 260 deletions(-)

diff --git a/src/adql/db/FunctionDef.java b/src/adql/db/FunctionDef.java
index b1e27b1..4a3d1f6 100644
--- a/src/adql/db/FunctionDef.java
+++ b/src/adql/db/FunctionDef.java
@@ -25,7 +25,11 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import adql.db.DBType.DBDatatype;
+import adql.parser.ADQLParser;
+import adql.parser.ADQLParserFactory;
+import adql.parser.ADQLParserFactory.ADQLVersion;
 import adql.parser.ParseException;
+import adql.parser.Token;
 import adql.parser.feature.LanguageFeature;
 import adql.query.operand.ADQLOperand;
 import adql.query.operand.function.ADQLFunction;
@@ -52,10 +56,10 @@ import adql.query.operand.function.UserDefinedFunction;
  * 	A description of this function may be set thanks to the public class
  * 	attribute {@link #description}.
  * </p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 2.0 (08/2020)
- * 
+ *
  * @since 1.3
  */
 public class FunctionDef implements Comparable<FunctionDef> {
@@ -264,9 +268,13 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	 * </p>
 	 *
 	 * @param fctName	Name of the function.
+	 *
+	 * @throws ParseException	If the given UDF name is invalid according to
+	 *                       	the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *                       	of the ADQL grammar.
 	 */
-	public FunctionDef(final String fctName) {
-		this(fctName, null, null);
+	public FunctionDef(final String fctName) throws ParseException {
+		this(fctName, null, null, null);
 	}
 
 	/**
@@ -277,13 +285,13 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	 * 	null) and <b>no parameter</b>.
 	 * </p>
 	 *
-	 * @param fctName		Name of the function.
+	 * @param fctName	Name of the function.
 	 * @param returnType	Return type of the function.
 	 *                  	<i>If NULL, this function will have no return
 	 *                  	type.</i>
 	 */
-	public FunctionDef(final String fctName, final DBType returnType) {
-		this(fctName, returnType, null);
+	public FunctionDef(final String fctName, final DBType returnType) throws ParseException {
+		this(fctName, returnType, null, null);
 	}
 
 	/**
@@ -295,19 +303,65 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	 * </p>
 	 * 
 	 * @param fctName		Name of the function.
-	 * @param params	Parameters of this function.
-	 *              	<i>If NULL or empty, this function will have no
-	 *              	parameter.</i>
+	 * @param params		Parameters of this function.
+	 *              		<i>If NULL or empty, this function will have no
+	 *              		parameter.</i>
+	 *
+	 * @throws ParseException	If the given UDF name is invalid according to
+	 *                       	the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *                       	of the ADQL grammar.
+	 */
+	public FunctionDef(final String fctName, final FunctionParam[] params) throws ParseException {
+		this(fctName, null, params, null);
+	}
+
+	/**
+	 * Create a function definition.
+	 *
+	 * @param fctName		Name of the function.
+	 * @param returnType	Return type of the function.
+	 *                  	<i>If NULL, this function will have no return type</i>
+	 * @param params		Parameters of this function.
+	 *              		<i>If NULL or empty, this function will have no
+	 *              		parameter.</i>
+	 *
+	 * @throws ParseException	If the given UDF name is invalid according to
+	 *                       	the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *                       	of the ADQL grammar.
 	 */
-	public FunctionDef(final String fctName, final FunctionParam[] params) {
-		this(fctName, null, params);
+	public FunctionDef(final String fctName, final DBType returnType, final FunctionParam[] params) throws ParseException {
+		this(fctName, returnType, params, ADQLParserFactory.DEFAULT_VERSION);
 	}
 
-	public FunctionDef(final String fctName, final DBType returnType, final FunctionParam[] params) {
+	/**
+	 * Create a function definition.
+	 *
+	 * @param fctName		Name of the function.
+	 * @param returnType	Return type of the function.
+	 *                  	<i>If NULL, this function will have no return type</i>
+	 * @param params		Parameters of this function.
+	 *              		<i>If NULL or empty, this function will have no
+	 *              		parameter.</i>
+	 * @param targetADQL	Targeted ADQL grammar's version.
+	 *              		<i>If NULL, the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *              		will be used. This parameter is used only to check
+	 *              		the UDF name.</i>
+	 *
+	 *
+	 * @throws ParseException	If the given UDF name is invalid according to
+	 *                       	the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *                       	of the ADQL grammar.
+	 *
+	 * @since 2.0
+	 */
+	public FunctionDef(final String fctName, final DBType returnType, final FunctionParam[] params, final ADQLVersion targetADQL) throws ParseException {
 		// Set the name:
 		if (fctName == null)
 			throw new NullPointerException("Missing name! Can not create this function definition.");
-		this.name = fctName;
+		this.name = fctName.trim();
+
+		// Ensure the function name is valid:
+		checkUDFName(fctName, targetADQL);
 
 		// Set the parameters:
 		this.params = (params == null || params.length == 0) ? null : params;
@@ -336,6 +390,56 @@ public class FunctionDef implements Comparable<FunctionDef> {
 		compareForm = bufCmp.toString();
 	}
 
+	/**
+	 * Check that the given UDF name is valid according to the ADQL grammar.
+	 *
+	 * @param fctName		Name of the UDF to check.
+	 * @param adqlVersion	Version of the targeted ADQL grammar.
+	 *              		<i>If NULL, the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *              		will be used.</i>
+	 *
+	 * @throws ParseException	If the given name is invalid.
+	 *
+	 * @since 2.0
+	 */
+	protected static void checkUDFName(final String fctName, final ADQLVersion adqlVersion) throws ParseException {
+		if (fctName == null)
+			throw new ParseException("Invalid UDF name: missing User Defined Function's name!");
+
+		ADQLParser parser = null;
+		Token[] tokens = new Token[0];
+
+		// Tokenize the given function name:
+		try {
+			parser = (new ADQLParserFactory()).createParser(adqlVersion == null ? ADQLParserFactory.DEFAULT_VERSION : adqlVersion);
+			tokens = parser.tokenize(fctName);
+		} catch(ParseException ex) {
+			throw new ParseException("Invalid UDF name: " + ex.getMessage());
+		}
+
+		// Ensure there is only one word:
+		if (tokens.length == 0)
+			throw new ParseException("Invalid UDF name: missing User Defined Function's name!");
+		else if (tokens.length > 1)
+			throw new ParseException("Invalid UDF name: too many words (a function name must be a single Regular Identifier)!");
+
+		// ...that it is a regular identifier:
+		if (!parser.isRegularIdentifier(tokens[0].image))
+			throw new ParseException("Invalid UDF name: \"" + fctName + "\" is not a Regular Identifier!");
+
+		// ...that it is not already an existing ADQL function name:
+		if (tokens[0].isFunctionName)
+			throw new ParseException("Invalid UDF name: \"" + fctName + "\" already exists in ADQL!");
+
+		// ...that it is not an ADQL reserved keyword:
+		if (tokens[0].adqlReserved)
+			throw new ParseException("Invalid UDF name: \"" + fctName + "\" is an ADQL Reserved Keyword!");
+
+		// ...and that it is neither an SQL reserver keyword:
+		if (tokens[0].sqlReserved)
+			throw new ParseException("Invalid UDF name: \"" + fctName + "\" is an SQL Reserved Keyword!");
+	}
+
 	/**
 	 * Tell whether this function returns a numeric.
 	 *
@@ -586,9 +690,49 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	 * @return	The object representation of the given string definition.
 	 *
 	 * @throws ParseException	If the given string has a wrong syntax or uses
-	 *                       	unknown types.
+	 *                       	unknown types,
+	 *                       	or if the function name is invalid according to
+	 *                       	the ADQL grammar.
 	 */
 	public static FunctionDef parse(final String strDefinition) throws ParseException {
+		return parse(strDefinition, null);
+	}
+
+	/**
+	 * Let parsing the serialized form of a function definition.
+	 *
+	 * <p>The expected syntax is <i>(items between brackets are optional)</i>:</p>
+	 * <pre>{fctName}([{param1Name} {param1Type}, ...])[ -> {returnType}]</pre>
+	 *
+	 * <p>
+	 * 	<em>This function must be able to parse functions as defined by
+	 * 	TAPRegExt (section 2.3).</em>
+	 * 	Hence, allowed parameter types and return types should be one of the
+	 * 	types listed by the UPLOAD section of the TAP recommendation document.
+	 * 	These types are listed in the enumeration object {@link DBDatatype}.
+	 * 	However, other types should be accepted like the common database
+	 * 	types...but it should be better to not rely on that since the conversion
+	 * 	of those types to TAP types should not be exactly what is expected
+	 * 	(because depending from the used DBMS); a default interpretation of
+	 * 	database types is nevertheless processed by this parser.
+	 * </p>
+	 *
+	 * @param strDefinition	Serialized function definition to parse.
+	 * @param targetADQL	Targeted ADQL grammar's version.
+	 *              		<i>If NULL, the {@link ADQLParserFactory#DEFAULT_VERSION default version}
+	 *              		will be used. This parameter is used only to check
+	 *              		the UDF name.</i>
+	 *
+	 * @return	The object representation of the given string definition.
+	 *
+	 * @throws ParseException	If the given string has a wrong syntax or uses
+	 *                       	unknown types,
+	 *                       	or if the function name is invalid according to
+	 *                       	the ADQL grammar.
+	 *
+	 * @since 2.0
+	 */
+	public static FunctionDef parse(final String strDefinition, final ADQLVersion targetADQL) throws ParseException {
 		if (strDefinition == null)
 			throw new NullPointerException("Missing string definition to build a FunctionDef!");
 
@@ -599,6 +743,9 @@ public class FunctionDef implements Comparable<FunctionDef> {
 			// Get the function name:
 			String fctName = m.group(1);
 
+			// Ensure the function name is valid:
+			checkUDFName(fctName, targetADQL);
+
 			// Parse and get the return type:
 			DBType returnType = null;
 			if (m.group(3) != null) {
diff --git a/src/adql/parser/ADQLParser.java b/src/adql/parser/ADQLParser.java
index ba40071..9292f13 100644
--- a/src/adql/parser/ADQLParser.java
+++ b/src/adql/parser/ADQLParser.java
@@ -134,4 +134,6 @@ public interface ADQLParser {
 
 	ADQLOperand StringExpression() throws ParseException;
 
+	public Token[] tokenize(final String expr) throws ParseException;
+
 }
diff --git a/src/adql/parser/ADQLParser200.java b/src/adql/parser/ADQLParser200.java
index a4b1e1d..c3f7eca 100644
--- a/src/adql/parser/ADQLParser200.java
+++ b/src/adql/parser/ADQLParser200.java
@@ -652,6 +652,24 @@ public class ADQLParser200 implements ADQLParser, ADQLParser200Constants {
 		}
 	}
 
+	/* TOKENIZATION FUNCTION */
+
+	@Override
+	public Token[] tokenize(final String expr) throws ParseException {
+		ADQLParser200TokenManager parser = new ADQLParser200TokenManager(new SimpleCharStream(new java.io.ByteArrayInputStream(expr.getBytes())));
+		try {
+			ArrayList<Token> tokens = new ArrayList<Token>();
+			Token token;
+			while(!isEnd((token = parser.getNextToken()))) {
+				tokens.add(token);
+			}
+			return tokens.toArray(new Token[tokens.size()]);
+		} catch(TokenMgrError err) {
+			// wrap such errors and propagate them:
+			throw new ParseException(err);
+		}
+	}
+
 	/* CORRECTION SUGGESTION */
 
 	/**
@@ -4459,49 +4477,6 @@ public class ADQLParser200 implements ADQLParser, ADQLParser200Constants {
 		}
 	}
 
-	private boolean jj_3R_17() {
-		Token xsp;
-		xsp = jj_scanpos;
-		if (jj_3R_34()) {
-			jj_scanpos = xsp;
-			if (jj_3R_35())
-				return true;
-		}
-		return false;
-	}
-
-	private boolean jj_3_17() {
-		if (jj_3R_29())
-			return true;
-		Token xsp;
-		xsp = jj_scanpos;
-		if (jj_scan_token(36))
-			jj_scanpos = xsp;
-		if (jj_scan_token(LIKE))
-			return true;
-		return false;
-	}
-
-	private boolean jj_3R_76() {
-		if (jj_scan_token(LEFT_PAR))
-			return true;
-		return false;
-	}
-
-	private boolean jj_3_3() {
-		if (jj_3R_17())
-			return true;
-		return false;
-	}
-
-	private boolean jj_3_16() {
-		if (jj_3R_22())
-			return true;
-		if (jj_scan_token(IS))
-			return true;
-		return false;
-	}
-
 	private boolean jj_3R_134() {
 		if (jj_scan_token(COMMA))
 			return true;
@@ -6186,6 +6161,49 @@ public class ADQLParser200 implements ADQLParser, ADQLParser200Constants {
 		return false;
 	}
 
+	private boolean jj_3R_17() {
+		Token xsp;
+		xsp = jj_scanpos;
+		if (jj_3R_34()) {
+			jj_scanpos = xsp;
+			if (jj_3R_35())
+				return true;
+		}
+		return false;
+	}
+
+	private boolean jj_3_17() {
+		if (jj_3R_29())
+			return true;
+		Token xsp;
+		xsp = jj_scanpos;
+		if (jj_scan_token(36))
+			jj_scanpos = xsp;
+		if (jj_scan_token(LIKE))
+			return true;
+		return false;
+	}
+
+	private boolean jj_3R_76() {
+		if (jj_scan_token(LEFT_PAR))
+			return true;
+		return false;
+	}
+
+	private boolean jj_3_3() {
+		if (jj_3R_17())
+			return true;
+		return false;
+	}
+
+	private boolean jj_3_16() {
+		if (jj_3R_22())
+			return true;
+		if (jj_scan_token(IS))
+			return true;
+		return false;
+	}
+
 	/** Generated Token Manager. */
 	public ADQLParser200TokenManager token_source;
 	SimpleCharStream jj_input_stream;
diff --git a/src/adql/parser/ADQLParser201.java b/src/adql/parser/ADQLParser201.java
index 29691f2..1d8499f 100644
--- a/src/adql/parser/ADQLParser201.java
+++ b/src/adql/parser/ADQLParser201.java
@@ -643,6 +643,24 @@ public class ADQLParser201 implements ADQLParser, ADQLParser201Constants {
 		}
 	}
 
+	/* TOKENIZATION FUNCTION */
+
+	@Override
+	public Token[] tokenize(final String expr) throws ParseException {
+		ADQLParser201TokenManager parser = new ADQLParser201TokenManager(new SimpleCharStream(new java.io.ByteArrayInputStream(expr.getBytes())));
+		try {
+			ArrayList<Token> tokens = new ArrayList<Token>();
+			Token token;
+			while(!isEnd((token = parser.getNextToken()))) {
+				tokens.add(token);
+			}
+			return tokens.toArray(new Token[tokens.size()]);
+		} catch(TokenMgrError err) {
+			// wrap such errors and propagate them:
+			throw new ParseException(err);
+		}
+	}
+
 	/* CORRECTION SUGGESTION */
 
 	/**
@@ -4476,57 +4494,6 @@ public class ADQLParser201 implements ADQLParser, ADQLParser201Constants {
 		}
 	}
 
-	private boolean jj_3R_17() {
-		Token xsp;
-		xsp = jj_scanpos;
-		if (jj_3R_34()) {
-			jj_scanpos = xsp;
-			if (jj_3R_35())
-				return true;
-		}
-		return false;
-	}
-
-	private boolean jj_3_17() {
-		if (jj_3R_29())
-			return true;
-		Token xsp;
-		xsp = jj_scanpos;
-		if (jj_scan_token(36))
-			jj_scanpos = xsp;
-		if (jj_scan_token(LIKE))
-			return true;
-		return false;
-	}
-
-	private boolean jj_3R_135() {
-		if (jj_scan_token(COMMA))
-			return true;
-		if (jj_3R_50())
-			return true;
-		return false;
-	}
-
-	private boolean jj_3R_76() {
-		if (jj_scan_token(LEFT_PAR))
-			return true;
-		return false;
-	}
-
-	private boolean jj_3_3() {
-		if (jj_3R_17())
-			return true;
-		return false;
-	}
-
-	private boolean jj_3_16() {
-		if (jj_3R_22())
-			return true;
-		if (jj_scan_token(IS))
-			return true;
-		return false;
-	}
-
 	private boolean jj_3_2() {
 		if (jj_3R_16())
 			return true;
@@ -6212,6 +6179,57 @@ public class ADQLParser201 implements ADQLParser, ADQLParser201Constants {
 		return false;
 	}
 
+	private boolean jj_3R_17() {
+		Token xsp;
+		xsp = jj_scanpos;
+		if (jj_3R_34()) {
+			jj_scanpos = xsp;
+			if (jj_3R_35())
+				return true;
+		}
+		return false;
+	}
+
+	private boolean jj_3_17() {
+		if (jj_3R_29())
+			return true;
+		Token xsp;
+		xsp = jj_scanpos;
+		if (jj_scan_token(36))
+			jj_scanpos = xsp;
+		if (jj_scan_token(LIKE))
+			return true;
+		return false;
+	}
+
+	private boolean jj_3R_135() {
+		if (jj_scan_token(COMMA))
+			return true;
+		if (jj_3R_50())
+			return true;
+		return false;
+	}
+
+	private boolean jj_3R_76() {
+		if (jj_scan_token(LEFT_PAR))
+			return true;
+		return false;
+	}
+
+	private boolean jj_3_3() {
+		if (jj_3R_17())
+			return true;
+		return false;
+	}
+
+	private boolean jj_3_16() {
+		if (jj_3R_22())
+			return true;
+		if (jj_scan_token(IS))
+			return true;
+		return false;
+	}
+
 	/** Generated Token Manager. */
 	public ADQLParser201TokenManager token_source;
 	SimpleCharStream jj_input_stream;
diff --git a/src/adql/parser/adqlGrammar200.jj b/src/adql/parser/adqlGrammar200.jj
index a0ac86c..360d0b8 100644
--- a/src/adql/parser/adqlGrammar200.jj
+++ b/src/adql/parser/adqlGrammar200.jj
@@ -670,6 +670,23 @@ public class ADQLParser200 implements ADQLParser {
 		}
 	}
 
+	/* TOKENIZATION FUNCTION */
+
+	public Token[] tokenize(final String expr) throws ParseException {
+		ADQLParser200TokenManager parser = new ADQLParser200TokenManager(new SimpleCharStream(new java.io.ByteArrayInputStream(expr.getBytes())));
+		try{
+			ArrayList<Token> tokens = new ArrayList<Token>();
+			Token token;
+			while(!isEnd((token=parser.getNextToken()))){
+				tokens.add(token);
+			}
+			return tokens.toArray(new Token[tokens.size()]);
+		}catch(TokenMgrError err){
+		    // wrap such errors and propagate them:
+			throw new ParseException(err);
+		}
+	}
+
 	/* CORRECTION SUGGESTION */
 
 	/**
diff --git a/src/adql/parser/adqlGrammar201.jj b/src/adql/parser/adqlGrammar201.jj
index a5db23b..98a75d6 100644
--- a/src/adql/parser/adqlGrammar201.jj
+++ b/src/adql/parser/adqlGrammar201.jj
@@ -664,6 +664,23 @@ public class ADQLParser201 implements ADQLParser {
 		}
 	}
 
+	/* TOKENIZATION FUNCTION */
+
+	public Token[] tokenize(final String expr) throws ParseException {
+		ADQLParser201TokenManager parser = new ADQLParser201TokenManager(new SimpleCharStream(new java.io.ByteArrayInputStream(expr.getBytes())));
+		try{
+			ArrayList<Token> tokens = new ArrayList<Token>();
+			Token token;
+			while(!isEnd((token=parser.getNextToken()))){
+				tokens.add(token);
+			}
+			return tokens.toArray(new Token[tokens.size()]);
+		}catch(TokenMgrError err){
+		    // wrap such errors and propagate them:
+			throw new ParseException(err);
+		}
+	}
+
 	/* CORRECTION SUGGESTION */
 
 	/**
diff --git a/src/adql/query/operand/function/DefaultUDF.java b/src/adql/query/operand/function/DefaultUDF.java
index b67b40d..326f5b8 100644
--- a/src/adql/query/operand/function/DefaultUDF.java
+++ b/src/adql/query/operand/function/DefaultUDF.java
@@ -26,6 +26,7 @@ import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.FunctionDef;
 import adql.db.FunctionDef.FunctionParam;
+import adql.parser.ParseException;
 import adql.parser.feature.LanguageFeature;
 import adql.query.ADQLList;
 import adql.query.ADQLObject;
@@ -49,7 +50,10 @@ public final class DefaultUDF extends UserDefinedFunction {
 
 	/** Define/Describe this user defined function.
 	 * This object gives the return type and the number and type of all
-	 * parameters. */
+	 * parameters.
+	 * <p><i><b>Note:</b> NULL if the function name is invalid. See
+	 * 	{@link FunctionDef#FunctionDef(String, DBType, FunctionParam[], adql.parser.ADQLParserFactory.ADQLVersion) FunctionDef.FunctionDef(..., ADQLVersion)}
+	 * for more details.</i></p> */
 	protected FunctionDef definition = null;
 
 	/** Its parsed parameters. */
@@ -163,11 +167,16 @@ public final class DefaultUDF extends UserDefinedFunction {
 		for(int i = 1; i <= parameters.size(); i++)
 			inputParams[i - 1] = new FunctionParam("param" + i, unknownType);
 
-		// Create the Function Definition:
-		FunctionDef fctDef = new FunctionDef(functionName, unknownType, inputParams);
+		try {
+			// Create the Function Definition:
+			FunctionDef fctDef = new FunctionDef(functionName, unknownType, inputParams);
 
-		// Finally create the LanguageFeature:
-		languageFeature = new LanguageFeature(fctDef);
+			// Finally create the LanguageFeature:
+			languageFeature = new LanguageFeature(fctDef);
+		} catch(ParseException pe) {
+			// TODO Invalid function name. TO LOG in some way!
+			languageFeature = null;
+		}
 	}
 
 	@Override
diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java
index 72ca3dd..7555e5d 100644
--- a/test/adql/db/TestDBChecker.java
+++ b/test/adql/db/TestDBChecker.java
@@ -290,10 +290,16 @@ public class TestDBChecker {
 		}
 
 		// DECLARE THE UDFs:
-		FunctionDef[] udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)) };
-		parser = parserFactory.createParser();
-		parser.getSupportedFeatures().allowAnyUdf(true);
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		FunctionDef[] udfs;
+		try {
+			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)) };
+			parser = parserFactory.createParser();
+			parser.getSupportedFeatures().allowAnyUdf(true);
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 
 		// Test again:
 		try {
@@ -340,11 +346,17 @@ public class TestDBChecker {
 		}
 
 		// Test with a UDF whose the class is specified ; the corresponding object in the ADQL tree must be replace by an instance of this class:
-		udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
-		udfs[0].setUDFClass(UDFToto.class);
-		parser = parserFactory.createParser();
-		parser.getSupportedFeatures().allowAnyUdf(true);
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		try {
+			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
+			udfs[0].setUDFClass(UDFToto.class);
+			parser = parserFactory.createParser();
+			parser.getSupportedFeatures().allowAnyUdf(true);
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
+
 		try {
 			ADQLQuery query = parser.parseQuery("SELECT toto('blabla') FROM foo;");
 			assertNotNull(query);
@@ -374,11 +386,17 @@ public class TestDBChecker {
 		}
 
 		// Test with UDF class constructor throwing an exception:
-		udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
-		udfs[0].setUDFClass(WrongUDFToto.class);
-		parser = parserFactory.createParser();
-		parser.getSupportedFeatures().allowAnyUdf(true);
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		try {
+			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
+			udfs[0].setUDFClass(WrongUDFToto.class);
+			parser = parserFactory.createParser();
+			parser.getSupportedFeatures().allowAnyUdf(true);
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
+
 		try {
 			parser.parseQuery("SELECT toto('blabla') FROM foo;");
 			fail("The set UDF class constructor has throw an error: this test should have failed!");
@@ -673,9 +691,15 @@ public class TestDBChecker {
 		}
 
 		// DECLARE SOME UDFs:
-		FunctionDef[] udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)), new FunctionDef("titi", new DBType(DBDatatype.REGION)) };
-		parser = parserFactory.createParser();
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		FunctionDef[] udfs;
+		try {
+			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)), new FunctionDef("titi", new DBType(DBDatatype.REGION)) };
+			parser = parserFactory.createParser();
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 
 		// Test the return type of the function TOTO generated by the parser:
 		try {
@@ -782,12 +806,18 @@ public class TestDBChecker {
 
 		// Try with functions wrapped on 2 levels:
 		// i.e. fct1('blabla', fct2(fct3('blabla')))
-		FunctionDef[] complexFcts = new FunctionDef[3];
-		complexFcts[0] = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)), new FunctionParam("num", new DBType(DBDatatype.INTEGER)) });
-		complexFcts[1] = new FunctionDef("fct2", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
-		complexFcts[2] = new FunctionDef("fct3", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
-		parser = parserFactory.createParser();
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(complexFcts)));
+		try {
+			FunctionDef[] complexFcts = new FunctionDef[3];
+			complexFcts[0] = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)), new FunctionParam("num", new DBType(DBDatatype.INTEGER)) });
+			complexFcts[1] = new FunctionDef("fct2", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
+			complexFcts[2] = new FunctionDef("fct3", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
+			parser = parserFactory.createParser();
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(complexFcts)));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
+
 		// With parameters of the good type:
 		try {
 			assertNotNull(parser.parseQuery("SELECT fct1('blabla', fct2(fct3('blabla'))) FROM foo"));
@@ -832,8 +862,13 @@ public class TestDBChecker {
 		}
 
 		// DECLARE THE UDF (while unknown functions are allowed):
-		parser = parserFactory.createParser();
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)) })));
+		try {
+			parser = parserFactory.createParser();
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)) })));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 
 		// Test the return type of the function generated by the parser:
 		try {
@@ -849,9 +884,14 @@ public class TestDBChecker {
 		}
 
 		// DECLARE UDFs WITH SAME NAMES BUT DIFFERENT TYPE OF ARGUMENT:
-		udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.VARCHAR)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.INTEGER)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.POINT)) }) };
-		parser = parserFactory.createParser();
-		parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		try {
+			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.VARCHAR)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.INTEGER)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.POINT)) }) };
+			parser = parserFactory.createParser();
+			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 
 		// Test the return type in function of the parameter:
 		try {
diff --git a/test/adql/db/TestFunctionDef.java b/test/adql/db/TestFunctionDef.java
index b46b73c..92bbfc0 100644
--- a/test/adql/db/TestFunctionDef.java
+++ b/test/adql/db/TestFunctionDef.java
@@ -10,6 +10,8 @@ import org.junit.Test;
 
 import adql.db.DBType.DBDatatype;
 import adql.db.FunctionDef.FunctionParam;
+import adql.parser.ADQLParserFactory;
+import adql.parser.ADQLParserFactory.ADQLVersion;
 import adql.parser.ParseException;
 import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
@@ -27,63 +29,83 @@ public class TestFunctionDef {
 	}
 
 	@Test
-	public void testIsString() {
-		for(DBDatatype type : DBDatatype.values()) {
-			switch(type) {
-				case CHAR:
-				case VARCHAR:
-				case TIMESTAMP:
-				case CLOB:
-					assertTrue(new FunctionDef("foo", new DBType(type)).isString);
-					break;
-				default:
-					assertFalse(new FunctionDef("foo", new DBType(type)).isString);
+	public void testIsString(){
+		try {
+			for(DBDatatype type : DBDatatype.values()){
+				switch(type){
+					case CHAR:
+					case VARCHAR:
+					case TIMESTAMP:
+					case CLOB:
+						assertTrue(new FunctionDef("foo", new DBType(type)).isString);
+						break;
+					default:
+						assertFalse(new FunctionDef("foo", new DBType(type)).isString);
+				}
 			}
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
 		}
 	}
 
 	@Test
-	public void testIsGeometry() {
-		for(DBDatatype type : DBDatatype.values()) {
-			switch(type) {
-				case POINT:
-				case REGION:
-					assertTrue(new FunctionDef("foo", new DBType(type)).isGeometry);
-					break;
-				default:
-					assertFalse(new FunctionDef("foo", new DBType(type)).isGeometry);
+	public void testIsGeometry(){
+		try {
+			for(DBDatatype type : DBDatatype.values()){
+				switch(type){
+					case POINT:
+					case REGION:
+						assertTrue(new FunctionDef("foo", new DBType(type)).isGeometry);
+						break;
+					default:
+						assertFalse(new FunctionDef("foo", new DBType(type)).isGeometry);
+				}
 			}
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
 		}
 	}
 
 	@Test
-	public void testIsNumeric() {
-		for(DBDatatype type : DBDatatype.values()) {
-			switch(type) {
-				case CHAR:
-				case VARCHAR:
-				case TIMESTAMP:
-				case POINT:
-				case REGION:
-				case CLOB:
-				case UNKNOWN:
-					assertFalse(new FunctionDef("foo", new DBType(type)).isNumeric);
-					break;
-				case UNKNOWN_NUMERIC:
-				default:
-					assertTrue(new FunctionDef("foo", new DBType(type)).isNumeric);
+	public void testIsNumeric(){
+		try {
+			for(DBDatatype type : DBDatatype.values()){
+				switch(type){
+					case CHAR:
+					case VARCHAR:
+					case TIMESTAMP:
+					case POINT:
+					case REGION:
+					case CLOB:
+					case UNKNOWN:
+						assertFalse(new FunctionDef("foo", new DBType(type)).isNumeric);
+						break;
+					case UNKNOWN_NUMERIC:
+					default:
+						assertTrue(new FunctionDef("foo", new DBType(type)).isNumeric);
+				}
 			}
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
 		}
 	}
 
 	@Test
-	public void testToString() {
-		assertEquals("fct1()", new FunctionDef("fct1").toString());
-		assertEquals("fct1() -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR)).toString());
-		assertEquals("fct1(foo DOUBLE) -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)) }).toString());
-		assertEquals("fct1(foo DOUBLE)", new FunctionDef("fct1", new FunctionParam[]{ new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)) }).toString());
-		assertEquals("fct1(foo DOUBLE, pt POINT) -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)), new FunctionParam("pt", new DBType(DBDatatype.POINT)) }).toString());
-		assertEquals("fct1(foo DOUBLE, pt POINT)", new FunctionDef("fct1", null, new FunctionParam[]{ new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)), new FunctionParam("pt", new DBType(DBDatatype.POINT)) }).toString());
+	public void testToString(){
+		try {
+			assertEquals("fct1()", new FunctionDef("fct1").toString());
+			assertEquals("fct1() -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR)).toString());
+			assertEquals("fct1(foo DOUBLE) -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE))}).toString());
+			assertEquals("fct1(foo DOUBLE)", new FunctionDef("fct1", new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE))}).toString());
+			assertEquals("fct1(foo DOUBLE, pt POINT) -> VARCHAR", new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)),new FunctionParam("pt", new DBType(DBDatatype.POINT))}).toString());
+			assertEquals("fct1(foo DOUBLE, pt POINT)", new FunctionDef("fct1", null, new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.DOUBLE)),new FunctionParam("pt", new DBType(DBDatatype.POINT))}).toString());
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 	}
 
 	@Test
@@ -269,66 +291,72 @@ public class TestFunctionDef {
 	}
 
 	@Test
-	public void testCompareToFunctionDef() {
-		// DEFINITION 1 :: fct1() -> VARCHAR
-		FunctionDef def1 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR));
+	public void testCompareToFunctionDef(){
+		try {
+			// DEFINITION 1 :: fct1() -> VARCHAR
+			FunctionDef def1 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR));
 
-		// TEST :: Identity test (def1 with def1): [EQUAL]
-		assertEquals(0, def1.compareTo(def1));
+			// TEST :: Identity test (def1 with def1): [EQUAL]
+			assertEquals(0, def1.compareTo(def1));
 
-		// TEST :: With a function having a different name and also no parameter: [GREATER]
-		assertEquals(1, def1.compareTo(new FunctionDef("fct0", new DBType(DBDatatype.VARCHAR))));
+			// TEST :: With a function having a different name and also no parameter: [GREATER]
+			assertEquals(1, def1.compareTo(new FunctionDef("fct0", new DBType(DBDatatype.VARCHAR))));
 
-		// TEST :: With a function having the same name, but a different return type: [EQUAL}
-		assertEquals(0, def1.compareTo(new FunctionDef("fct1", new DBType(DBDatatype.INTEGER))));
+			// TEST :: With a function having the same name, but a different return type: [EQUAL}
+			assertEquals(0, def1.compareTo(new FunctionDef("fct1", new DBType(DBDatatype.INTEGER))));
 
-		// TEST :: With a function having the same name, but 2 parameters: [LESS (6 characters: ø against 100100)]
-		assertEquals(-6, def1.compareTo(new FunctionDef("fct1", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("foo", new DBType(DBDatatype.INTEGER)), new FunctionParam("foo", new DBType(DBDatatype.INTEGER)) })));
+			// TEST :: With a function having the same name, but 2 parameters: [LESS (6 characters: ø against 100100)]
+			assertEquals(-6, def1.compareTo(new FunctionDef("fct1", new DBType(DBDatatype.INTEGER), new FunctionParam[]{new FunctionParam("foo", new DBType(DBDatatype.INTEGER)),new FunctionParam("foo", new DBType(DBDatatype.INTEGER))})));
 
-		// DEFINITION 1 :: fct1(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR
-		def1 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)), new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE)) });
+			// DEFINITION 1 :: fct1(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR
+			def1 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE))});
 
-		// TEST :: Identity test (def1 with def1): [EQUAL]
-		assertEquals(0, def1.compareTo(def1));
+			// TEST :: Identity test (def1 with def1): [EQUAL]
+			assertEquals(0, def1.compareTo(def1));
 
-		// DEFINITION 2 :: fct1(foo1 CHAR(12), foo2 VARCHAR) -> VARCHAR
-		FunctionDef def2 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)), new FunctionParam("foo2", new DBType(DBDatatype.VARCHAR)) });
+			// DEFINITION 2 :: fct1(foo1 CHAR(12), foo2 VARCHAR) -> VARCHAR
+			FunctionDef def2 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.VARCHAR))});
 
-		// TEST :: Identity test (def2 with def2): [EQUAL]
-		assertEquals(0, def2.compareTo(def2));
+			// TEST :: Identity test (def2 with def2): [EQUAL]
+			assertEquals(0, def2.compareTo(def2));
 
-		// TEST :: Same name, but different type for the last parameter only: [GREATER (because Numeric = 10 > String = 01)]
-		assertEquals(1, def1.compareTo(def2));
+			// TEST :: Same name, but different type for the last parameter only: [GREATER (because Numeric = 10 > String = 01)]
+			assertEquals(1, def1.compareTo(def2));
 
-		// DEFINITION 2 :: fct2(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR
-		def2 = new FunctionDef("fct2", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)), new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE)) });
+			// DEFINITION 2 :: fct2(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR
+			def2 = new FunctionDef("fct2", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE))});
 
-		// TEST :: Identity test (def2 with def2): [EQUAL]
-		assertEquals(0, def2.compareTo(def2));
+			// TEST :: Identity test (def2 with def2): [EQUAL]
+			assertEquals(0, def2.compareTo(def2));
 
-		// TEST :: Different name but same parameters: [LESS]
-		assertEquals(-1, def1.compareTo(def2));
+			// TEST :: Different name but same parameters: [LESS]
+			assertEquals(-1, def1.compareTo(def2));
 
-		// DEFINITION 2 :: fct1(foo1 CHAR(12), foo2 POINT) -> VARCHAR
-		def2 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)), new FunctionParam("foo2", new DBType(DBDatatype.POINT)) });
+			// DEFINITION 2 :: fct1(foo1 CHAR(12), foo2 POINT) -> VARCHAR
+			def2 = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.POINT))});
 
-		// TEST :: Identity test (def2 with def2): [EQUAL]
-		assertEquals(0, def2.compareTo(def2));
+			// TEST :: Identity test (def2 with def2): [EQUAL]
+			assertEquals(0, def2.compareTo(def2));
 
-		// TEST :: Same name, but different type for the last parameter only: [GREATER]
-		assertEquals(1, def1.compareTo(def2));
+			// TEST :: Same name, but different type for the last parameter only: [GREATER]
+			assertEquals(1, def1.compareTo(def2));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 	}
 
 	@Test
-	public void testCompareToADQLFunction() {
+	public void testCompareToADQLFunction(){
+		try {
 		// DEFINITION :: fct1() -> VARCHAR
 		FunctionDef def = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR));
 
 		// TEST :: NULL:
-		try {
+		try{
 			def.compareTo((ADQLFunction)null);
 			fail("Missing ADQL function for comparison with FunctionDef!");
-		} catch(Exception e) {
+		}catch(Exception e){
 			assertTrue(e instanceof NullPointerException);
 			assertEquals("Missing ADQL function with which comparing this function definition!", e.getMessage());
 		}
@@ -340,33 +368,149 @@ public class TestFunctionDef {
 		assertEquals(1, def.compareTo(new DefaultUDF("fct0", null)));
 
 		// TEST :: "fct1(12.3, 3.14)": [LESS (of 2 params)]
-		assertEquals(-2, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{ new NumericConstant(12.3), new NumericConstant(3.14) })));
+		assertEquals(-2, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{new NumericConstant(12.3),new NumericConstant(3.14)})));
 
 		// DEFINITION :: fct1(foo1 CHAR(12), foo2 DOUBLE) -> VARCHAR
-		def = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)), new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE)) });
+		def = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{new FunctionParam("foo1", new DBType(DBDatatype.CHAR, 12)),new FunctionParam("foo2", new DBType(DBDatatype.DOUBLE))});
 
 		// TEST :: "fct1('blabla', 'blabla2')": [GREATER (because the second param is numeric and Numeric = 10 > String = 01)]
-		assertEquals(1, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{ new StringConstant("blabla"), new StringConstant("blabla2") })));
+		assertEquals(1, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{new StringConstant("blabla"),new StringConstant("blabla2")})));
 
 		// TEST :: "fct1('blabla', POINT('COORDSYS', 1.2, 3.4))": [GREATER (same reason ; POINT is considered as a String)]
-		try {
-			assertEquals(1, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{ new StringConstant("blabla"), new PointFunction(new StringConstant("COORDSYS"), new NumericConstant(1.2), new NumericConstant(3.4)) })));
-		} catch(Exception e) {
+		try{
+			assertEquals(1, def.compareTo(new DefaultUDF("fct1", new ADQLOperand[]{new StringConstant("blabla"),new PointFunction(new StringConstant("COORDSYS"), new NumericConstant(1.2), new NumericConstant(3.4))})));
+		}catch(Exception e){
 			e.printStackTrace();
 			fail();
 		}
 
 		// Test with an UNKNOWN numeric type:
 		// TEST :: "fct0(foo)", where foo is a simple UNKNOWN [EQUAL]
-		FunctionDef def0 = new FunctionDef("fct0", null, new FunctionParam[]{ new FunctionParam("whatever", new DBType(DBDatatype.VARCHAR)) });
+		FunctionDef def0 = new FunctionDef("fct0", null, new FunctionParam[]{new FunctionParam("whatever", new DBType(DBDatatype.VARCHAR))});
 		DefaultDBColumn dbcol = new DefaultDBColumn("foo", new DefaultDBTable("toto"));
 		dbcol.setDatatype(new DBType(DBDatatype.UNKNOWN));
 		ADQLColumn col = new ADQLColumn("foo");
 		col.setDBLink(dbcol);
-		assertEquals(0, def0.compareTo(new DefaultUDF("fct0", new ADQLOperand[]{ col })));
+		assertEquals(0, def0.compareTo(new DefaultUDF("fct0", new ADQLOperand[]{col})));
 		// TEST :: "fct0(foo)", where foo is an UNKNOWN NUMERIC [LESS]
 		dbcol.setDatatype(new DBType(DBDatatype.UNKNOWN_NUMERIC));
-		assertEquals(-1, def0.compareTo(new DefaultUDF("fct0", new ADQLOperand[]{ col })));
+		assertEquals(-1, def0.compareTo(new DefaultUDF("fct0", new ADQLOperand[]{col})));
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
+	}
+
+	@Test
+	public void testCheckUDFName() {
+
+		// TEST: no function name
+		String[] missingNames = new String[]{ null, "", " ", "	", " 	", "\n" };
+		for(String n : missingNames) {
+			try {
+				FunctionDef.checkUDFName(n, null);
+				fail("Impossible to create a UDF with no name!");
+			} catch(Exception ex) {
+				assertEquals(ParseException.class, ex.getClass());
+				assertEquals("Invalid UDF name: missing User Defined Function's name!", ex.getMessage());
+			}
+		}
+
+		// TEST: more than one word
+		try {
+			FunctionDef.checkUDFName("too many words", null);
+			fail("Impossible to create a UDF with more than a single word!");
+		} catch(Exception ex) {
+			assertEquals(ParseException.class, ex.getClass());
+			assertEquals("Invalid UDF name: too many words (a function name must be a single Regular Identifier)!", ex.getMessage());
+		}
+
+		// TEST: not a regular identifier
+		String[] notRegularIDs = new String[]{ "_foo", "4foo" };
+		for(String n : notRegularIDs) {
+			try {
+				FunctionDef.checkUDFName(n, null);
+				fail("Impossible to create a UDF whose the name is not a regular identifier!");
+			} catch(Exception ex) {
+				assertEquals(ParseException.class, ex.getClass());
+				assertEquals("Invalid UDF name: \"" + n + "\" is not a Regular Identifier!", ex.getMessage());
+			}
+		}
+
+		// TEST: existing ADQL function
+		String[] existingFctNames = new String[]{ "point", "POINT", "MaX", "round" };
+		for(String n : existingFctNames) {
+			try {
+				FunctionDef.checkUDFName(n, null);
+				fail("Impossible to create a UDF with the name of an existing ADQL function!");
+			} catch(Exception ex) {
+				assertEquals(ParseException.class, ex.getClass());
+				assertEquals("Invalid UDF name: \"" + n + "\" already exists in ADQL!", ex.getMessage());
+			}
+		}
+
+		// TEST: reserved ADQL keyword
+		String[] reservedADQLKeywords = new String[]{ "select", "FroM", "as", "JOIN" };
+		for(String n : reservedADQLKeywords) {
+			try {
+				FunctionDef.checkUDFName(n, null);
+				fail("Impossible to create a UDF whose the name is an ADQL reserved keyword!");
+			} catch(Exception ex) {
+				assertEquals(ParseException.class, ex.getClass());
+				assertEquals("Invalid UDF name: \"" + n + "\" is an ADQL Reserved Keyword!", ex.getMessage());
+			}
+		}
+
+		// TEST: reserved SQL keyword
+		String[] reservedSQLKeywords = new String[]{ "Insert", "INTO", "upDate", "date" };
+		for(String n : reservedSQLKeywords) {
+			try {
+				FunctionDef.checkUDFName(n, null);
+				fail("Impossible to create a UDF whose the name is an SQL reserved keyword!");
+			} catch(Exception ex) {
+				assertEquals(ParseException.class, ex.getClass());
+				assertEquals("Invalid UDF name: \"" + n + "\" is an SQL Reserved Keyword!", ex.getMessage());
+			}
+		}
+
+		final String MSG_LOWER_2_0 = "Invalid UDF name: \"lower\" is an SQL Reserved Keyword!";
+		final String MSG_LOWER_2_1 = "Invalid UDF name: \"lower\" already exists in ADQL!";
+
+		/* TEST: the ADQL version is taken into account (the error message for
+		 *       the function LOWER existing only in ADQL-2.1 should be
+		 *       different): */
+		try {
+			FunctionDef.checkUDFName("lower", ADQLVersion.V2_0);
+			fail("LOWER is supposed to be a reserved keyword!");
+		} catch(Exception e) {
+			assertEquals(ParseException.class, e.getClass());
+			assertEquals(MSG_LOWER_2_0, e.getMessage());
+		}
+		try {
+			FunctionDef.checkUDFName("lower", ADQLVersion.V2_1);
+			fail("LOWER is supposed to be a reserved keyword!");
+		} catch(Exception e) {
+			assertEquals(ParseException.class, e.getClass());
+			assertEquals(MSG_LOWER_2_1, e.getMessage());
+		}
+
+		// TEST: no ADQL version = default version:
+		try {
+			FunctionDef.checkUDFName("lower", null);
+			fail("LOWER is supposed to be a reserved keyword!");
+		} catch(Exception e) {
+			assertEquals(ParseException.class, e.getClass());
+			switch(ADQLParserFactory.DEFAULT_VERSION) {
+				case V2_0:
+					assertEquals(MSG_LOWER_2_0, e.getMessage());
+					break;
+				case V2_1:
+					assertEquals(MSG_LOWER_2_1, e.getMessage());
+					break;
+				default:
+					fail("New ADQL Version not yet taken into account in this JUnit Test!");
+			}
+		}
 	}
 
 	@Test
diff --git a/test/adql/parser/feature/TestFeatureSet.java b/test/adql/parser/feature/TestFeatureSet.java
index 3d256aa..78e050a 100644
--- a/test/adql/parser/feature/TestFeatureSet.java
+++ b/test/adql/parser/feature/TestFeatureSet.java
@@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.HashSet;
 import java.util.Iterator;
@@ -14,6 +15,7 @@ import org.junit.Test;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.FunctionDef;
+import adql.parser.ParseException;
 import adql.query.ColumnReference;
 import adql.query.operand.function.geometry.BoxFunction;
 import adql.query.operand.function.geometry.PolygonFunction;
@@ -216,20 +218,25 @@ public class TestFeatureSet {
 
 	@Test
 	public void testUnsupportAll() {
-		FeatureSet set = new FeatureSet(true);
+		try {
+			FeatureSet set = new FeatureSet(true);
 
-		/* here is a custom Language Feature (i.e. not part of the
-		 * availableFeatures list): */
-		set.support(new LanguageFeature(new FunctionDef("foo", new DBType(DBDatatype.SMALLINT), new FunctionDef.FunctionParam[]{ new FunctionDef.FunctionParam("", new DBType(DBDatatype.VARCHAR)) })));
+			/* here is a custom Language Feature (i.e. not part of the
+			 * availableFeatures list): */
+			set.support(new LanguageFeature(new FunctionDef("foo", new DBType(DBDatatype.SMALLINT), new FunctionDef.FunctionParam[]{ new FunctionDef.FunctionParam("", new DBType(DBDatatype.VARCHAR)) })));
 
-		// unsupport all currently supported features:
-		set.unsupportAll();
+			// unsupport all currently supported features:
+			set.unsupportAll();
 
-		// ensure the list of supported features is really empty:
-		assertEquals(0, set.supportedFeatures.size());
+			// ensure the list of supported features is really empty:
+			assertEquals(0, set.supportedFeatures.size());
 
-		// ...and that no non-declared UDF is allowed:
-		assertFalse(set.isAnyUdfAllowed());
+			// ...and that no non-declared UDF is allowed:
+			assertFalse(set.isAnyUdfAllowed());
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 	}
 
 	@Test
diff --git a/test/adql/query/TestADQLQuery.java b/test/adql/query/TestADQLQuery.java
index 272622e..6478cb1 100644
--- a/test/adql/query/TestADQLQuery.java
+++ b/test/adql/query/TestADQLQuery.java
@@ -14,6 +14,7 @@ import org.junit.Test;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.FunctionDef;
+import adql.parser.ParseException;
 import adql.query.constraint.Comparison;
 import adql.query.constraint.ComparisonOperator;
 import adql.query.constraint.ConstraintsGroup;
@@ -50,7 +51,7 @@ public class TestADQLQuery {
 	private List<ADQLColumn> typeObjColumns = new ArrayList<ADQLColumn>(3);
 
 	@Before
-	public void setUp(){
+	public void setUp() {
 		query = new ADQLQuery();
 		columns.clear();
 		typeObjColumns.clear();
@@ -102,12 +103,12 @@ public class TestADQLQuery {
 	}
 
 	@Test
-	public void testADQLQuery(){
+	public void testADQLQuery() {
 		assertEquals("SELECT (O.nameObj || ' (' || O.typeObj || ')') AS Nom objet , O.ra , O.dec\nFROM truc.ObsCore AS O\nWHERE ra/dec > 1 AND (typeObj = 'Star' OR typeObj LIKE 'Galaxy*')\nORDER BY 1 DESC", query.toADQL());
 	}
 
 	@Test
-	public void testSearch(){
+	public void testSearch() {
 		ISearchHandler sHandler = new SearchColumnHandler(false);
 		Iterator<ADQLObject> results = query.search(sHandler);
 		assertEquals(columns.size(), sHandler.getNbMatch());
@@ -116,15 +117,15 @@ public class TestADQLQuery {
 	}
 
 	@Test
-	public void testReplace(){
-		IReplaceHandler sHandler = new SimpleReplaceHandler(false, false){
+	public void testReplace() {
+		IReplaceHandler sHandler = new SimpleReplaceHandler(false, false) {
 			@Override
-			protected boolean match(ADQLObject obj){
+			protected boolean match(ADQLObject obj) {
 				return (obj instanceof ADQLColumn) && (((ADQLColumn)obj).getColumnName().equalsIgnoreCase("typeObj"));
 			}
 
 			@Override
-			public ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException{
+			public ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException {
 				return new ADQLColumn("NewTypeObj");
 			}
 		};
@@ -138,7 +139,7 @@ public class TestADQLQuery {
 	}
 
 	@Test
-	public void testTypeResultingColumns(){
+	public void testTypeResultingColumns() {
 		ADQLQuery query = new ADQLQuery();
 		query.setFrom(new ADQLTable("foo"));
 		ClauseSelect select = new ClauseSelect();
@@ -156,12 +157,12 @@ public class TestADQLQuery {
 		assertEquals(DBDatatype.UNKNOWN_NUMERIC, query.getResultingColumns()[0].getDatatype().type);
 
 		// Test with a math function:
-		try{
+		try {
 			select.clear();
 			select.add(new MathFunction(MathFunctionType.SQRT, new ADQLColumn("col1")));
 			assertEquals(1, query.getResultingColumns().length);
 			assertEquals(DBDatatype.UNKNOWN_NUMERIC, query.getResultingColumns()[0].getDatatype().type);
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("The mathematical function SQRT is well defined. This error should have occurred.");
 		}
@@ -188,20 +189,20 @@ public class TestADQLQuery {
 		assertEquals(DBDatatype.VARCHAR, query.getResultingColumns()[0].getDatatype().type);
 
 		// Test with a POINT:
-		try{
+		try {
 			select.clear();
 			select.add(new PointFunction(new StringConstant(""), new ADQLColumn("ra"), new ADQLColumn("dec")));
 			select.add(new CentroidFunction(new GeometryValue<GeometryFunction>(new ADQLColumn("aRegion"))));
 			assertEquals(2, query.getResultingColumns().length);
 			for(int i = 0; i < 2; i++)
 				assertEquals(DBDatatype.POINT, query.getResultingColumns()[i].getDatatype().type);
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("The POINT function is well defined. This error should have occurred.");
 		}
 
 		// Test with a REGION (CIRCLE, BOX, POLYGON and REGION functions):
-		try{
+		try {
 			select.clear();
 			select.add(new CircleFunction(new StringConstant(""), new ADQLColumn("ra"), new ADQLColumn("dec"), new NumericConstant(1)));
 			select.add(new BoxFunction(new StringConstant(""), new ADQLColumn("ra"), new ADQLColumn("dec"), new NumericConstant(10), new NumericConstant(20)));
@@ -217,7 +218,7 @@ public class TestADQLQuery {
 			assertEquals(4, query.getResultingColumns().length);
 			for(int i = 0; i < 4; i++)
 				assertEquals(DBDatatype.REGION, query.getResultingColumns()[i].getDatatype().type);
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("The geometrical functions are well defined. This error should have occurred.");
 		}
@@ -229,12 +230,16 @@ public class TestADQLQuery {
 		assertNull(query.getResultingColumns()[0].getDatatype());
 
 		// Test with a UDF having a definition:
-		select.clear();
-		DefaultUDF udf = new DefaultUDF("foo", new ADQLOperand[0]);
-		udf.setDefinition(new FunctionDef("foo", new DBType(DBDatatype.INTEGER)));
-		select.add(udf);
-		assertEquals(1, query.getResultingColumns().length);
-		assertEquals(DBDatatype.INTEGER, query.getResultingColumns()[0].getDatatype().type);
-
+		try {
+			select.clear();
+			DefaultUDF udf = new DefaultUDF("foo", new ADQLOperand[0]);
+			udf.setDefinition(new FunctionDef("foo", new DBType(DBDatatype.INTEGER)));
+			select.add(udf);
+			assertEquals(1, query.getResultingColumns().length);
+			assertEquals(DBDatatype.INTEGER, query.getResultingColumns()[0].getDatatype().type);
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+		}
 	}
 }
-- 
GitLab