Commit 3509775a authored by Grégory Mantelet's avatar Grégory Mantelet
Browse files

[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.
parent 5c5b4681
Loading
Loading
Loading
Loading
+162 −15
Original line number Diff line number Diff line
@@ -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;
@@ -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);
	}

	/**
@@ -282,8 +290,8 @@ public class FunctionDef implements Comparable<FunctionDef> {
	 *                  	<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);
	}

	/**
@@ -298,16 +306,62 @@ public class FunctionDef implements Comparable<FunctionDef> {
	 * @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) {
+2 −0
Original line number Diff line number Diff line
@@ -134,4 +134,6 @@ public interface ADQLParser {

	ADQLOperand StringExpression() throws ParseException;

	public Token[] tokenize(final String expr) throws ParseException;

}
+61 −43
Original line number Diff line number Diff line
@@ -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;
+69 −51
Original line number Diff line number Diff line
@@ -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;
+17 −0
Original line number Diff line number Diff line
@@ -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 */

	/**
Loading