From 0003e3436a0684456102015cfe8babbc86a864e0 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Fri, 4 Mar 2016 20:10:26 +0100
Subject: [PATCH] [ADQL] Set a type to a query's resulting column when it is
 not originally a column. This is easily possible for concatenations, string
 constants and User Defined Functions having a FunctionDef. A new special
 datatype was needed for numeric functions and operations: UNKNOWN_NUMERIC.
 This special type can not be set with FunctionDef.parse(...) and it behaves
 exactly like the type UNKNOWN, except that DBType.isNumeric() returns true
 (as .isUnknown()). Thus, while writing the metadata of a result in TAP,
 nothing changes: an UNKNOWN_NUMERIC type will be processed similarly as an
 UNKNOWN type: to use the type returned from the database ResultSet or to set
 VARCHAR. (no modification of TAP was needed for that)

---
 src/adql/db/DBType.java                |  47 ++++++----
 src/adql/db/FunctionDef.java           |   4 +-
 src/adql/query/ADQLQuery.java          |  45 +++++++++-
 src/adql/query/operand/ADQLColumn.java |  10 +--
 test/adql/TestADQLQuery.java           | 115 +++++++++++++++++++++++++
 test/adql/db/TestFunctionDef.java      |  14 +++
 test/adql/parser/UnknownTypes.java     |  35 ++++++--
 7 files changed, 233 insertions(+), 37 deletions(-)

diff --git a/src/adql/db/DBType.java b/src/adql/db/DBType.java
index ca27139..b81d608 100644
--- a/src/adql/db/DBType.java
+++ b/src/adql/db/DBType.java
@@ -16,11 +16,10 @@ package adql.db;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2014-2015 - Astronomisches Rechen Institut (ARI)
+ * Copyright 2014-2016 - Astronomisches Rechen Institut (ARI)
  */
 
 /**
- * 
  * <p>
  * 	Describe a full column type as it is described in the IVOA document of TAP.
  * 	Thus, this object contains 2 attributes: <code>type</code> (or datatype) and <code>length</code> (or size).
@@ -32,7 +31,7 @@ package adql.db;
  * It is used to set the attribute type/datatype of this class.</p>
  *  
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 1.4 (08/2015)
+ * @version 1.4 (03/2016)
  * @since 1.3
  */
 public class DBType {
@@ -41,13 +40,21 @@ public class DBType {
 	 * List of all datatypes declared in the IVOA recommendation of TAP (in the section UPLOAD).
 	 * 
 	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.4 (06/2015)
+	 * @version 1.4 (03/2016)
 	 * @since 1.3
 	 */
 	public static enum DBDatatype{
 		SMALLINT, INTEGER, BIGINT, REAL, DOUBLE, BINARY, VARBINARY, CHAR, VARCHAR, BLOB, CLOB, TIMESTAMP, POINT, REGION,
-		/** @since 1.4 */
-		UNKNOWN;
+		/** Type to use when the precise datatype is unknown. 
+		 * @since 1.4 */
+		UNKNOWN,
+		/** <p>Type to use when the type is known as numeric but there is no precise datatype
+		 * (e.g. double, float, integer, ...).</p>
+		 * <p>It is particularly used when creating a {@link DefaultDBColumn} from an ADQL function
+		 * or operation while listing resulting columns of a sub-query.</p>
+		 * <p>This type is similar to {@link #UNKNOWN}.</p>
+		 * @since 1.4 */
+		UNKNOWN_NUMERIC;
 
 		/** String to return when {@link #toString()} is called.
 		 * @since 1.4*/
@@ -60,11 +67,11 @@ public class DBType {
 
 		/**
 		 * <p>This function lets define the name of the type as provided
-		 * <b>ONLY FOR {@link #UNKNOWN} {@link DBDatatype}</b>.</p>
+		 * <b>ONLY FOR {@link #UNKNOWN} and {@link #UNKNOWN_NUMERIC} {@link DBDatatype}s</b>.</p>
 		 * 
 		 * <p><i><b>Important:</b>
-		 * 	If this {@link DBDatatype} is not {@link #UNKNOWN} or if the given name is NULL or empty,
-		 * 	this function has no effect.
+		 * 	If this {@link DBDatatype} is not {@link #UNKNOWN} or {@link #UNKNOWN_NUMERIC} or
+		 * 	if the given name is NULL or empty, this function has no effect.
 		 * </i></p>
 		 * 
 		 * @param typeName	User type name.
@@ -72,7 +79,7 @@ public class DBType {
 		 * @since 1.4
 		 */
 		public void setCustomType(final String typeName){
-			if (this == UNKNOWN && typeName != null && typeName.trim().length() > 0)
+			if ((this == UNKNOWN || this == UNKNOWN_NUMERIC) && typeName != null && typeName.trim().length() > 0)
 				strExp = "?" + typeName.trim() + "?";
 		}
 	}
@@ -121,7 +128,8 @@ public class DBType {
 	 * 	Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
 	 * 	But, in order to avoid incorrect operation while expecting a numeric although the type is unknown
 	 * 	and is in fact not really a numeric, this function will return <code>false</code> if the type is
-	 * 	{@link DBDatatype#UNKNOWN UNKNOWN}.
+	 * 	{@link DBDatatype#UNKNOWN UNKNOWN} <b>BUT</b> <code>true</code> if
+	 * 	{@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
 	 * </i></p>
 	 * 
 	 * @return	<code>true</code> if this type is a numeric, <code>false</code> otherwise.
@@ -138,6 +146,7 @@ public class DBType {
 			case BINARY:
 			case VARBINARY:
 			case BLOB:
+			case UNKNOWN_NUMERIC:
 				return true;
 			default:
 				return false;
@@ -155,7 +164,7 @@ public class DBType {
 	 * 	Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
 	 * 	But, in order to avoid incorrect operation while expecting a binary although the type is unknown
 	 * 	and is in fact not really a binary, this function will return <code>false</code> if the type is
-	 * 	{@link DBDatatype#UNKNOWN UNKNOWN}.
+	 * 	{@link DBDatatype#UNKNOWN UNKNOWN} or {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
 	 * </i></p>
 	 * 
 	 * @return	<code>true</code> if this type is a binary, <code>false</code> otherwise.
@@ -183,7 +192,7 @@ public class DBType {
 	 * 	Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
 	 * 	But, in order to avoid incorrect operation while expecting a string although the type is unknown
 	 * 	and is in fact not really a string, this function will return <code>false</code> if the type is
-	 * 	{@link DBDatatype#UNKNOWN UNKNOWN}.
+	 * 	{@link DBDatatype#UNKNOWN UNKNOWN} or {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}
 	 * </i></p>
 	 * 
 	 * @return	<code>true</code> if this type is a string, <code>false</code> otherwise.
@@ -211,7 +220,7 @@ public class DBType {
 	 * 	Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything.
 	 * 	But, in order to avoid incorrect operation while expecting a geometry although the type is unknown
 	 * 	and is in fact not really a geometry, this function will return <code>false</code> if the type is
-	 * 	{@link DBDatatype#UNKNOWN UNKNOWN}.
+	 * 	{@link DBDatatype#UNKNOWN UNKNOWN} or {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
 	 * </i></p>
 	 * 
 	 * @return	<code>true</code> if this type is a geometry, <code>false</code> otherwise.
@@ -223,8 +232,8 @@ public class DBType {
 	/**
 	 * <p>Tell whether this type has been resolved or not.</p>
 	 * 
-	 * <p><i>Concerned type:
-	 * 	{@link DBDatatype#UNKNOWN UNKNOWN}.
+	 * <p><i>Concerned types:
+	 * 	{@link DBDatatype#UNKNOWN UNKNOWN} and {@link DBDatatype#UNKNOWN_NUMERIC UNKNOWN_NUMERIC}.
 	 * </i></p>
 	 * 
 	 * @return	<code>true</code> if this type has NOT been resolved, <code>false</code> otherwise.
@@ -232,7 +241,7 @@ public class DBType {
 	 * @since 1.4
 	 */
 	public boolean isUnknown(){
-		return type == DBDatatype.UNKNOWN;
+		return type == DBDatatype.UNKNOWN || type == DBDatatype.UNKNOWN_NUMERIC;
 	}
 
 	/**
@@ -240,8 +249,8 @@ public class DBType {
 	 * 
 	 * <p>
 	 * 	Two {@link DBType}s are said compatible if they are both binary, numeric, geometric or string.
-	 * 	If one of the two types is {@link DBDatatype#UNKNOWN unknown}, this function will consider them
-	 * 	as compatible and will return <code>true</code>.
+	 * 	If one of the two types is {@link DBDatatype#UNKNOWN unknown} or {@link DBDatatype#UNKNOWN_NUMERIC unknown_numeric},
+	 * 	this function will consider them as compatible and will return <code>true</code>.
 	 * </p>
 	 * 
 	 * @param t	The type to compare to.
diff --git a/src/adql/db/FunctionDef.java b/src/adql/db/FunctionDef.java
index b6a160b..c334c8b 100644
--- a/src/adql/db/FunctionDef.java
+++ b/src/adql/db/FunctionDef.java
@@ -59,7 +59,7 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	/** Rough regular expression for a function return type or a parameter type.
 	 * The exact type is not checked here ; just the type name syntax is tested, not its value.
 	 * This regular expression allows a type to have exactly one parameter (which is generally the length of a character or binary string. */
-	protected final static String typeRegExp = "([a-zA-Z]+[ 0-9a-zA-Z]*)(\\(\\s*([0-9]+)\\s*\\))?";
+	protected final static String typeRegExp = "([a-zA-Z_]+[ 0-9a-zA-Z_]*)(\\(\\s*([0-9]+)\\s*\\))?";
 	/** Rough regular expression for a function parameters' list. */
 	protected final static String fctParamsRegExp = "\\s*[^,]+\\s*(,\\s*[^,]+\\s*)*";
 	/** Rough regular expression for a function parameter: a name (see {@link #regularIdentifierRegExp}) and a type (see {@link #typeRegExp}). */
@@ -350,7 +350,7 @@ public class FunctionDef implements Comparable<FunctionDef> {
 	 * <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 DBType}.
+	 * 	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.
diff --git a/src/adql/query/ADQLQuery.java b/src/adql/query/ADQLQuery.java
index 1372e71..805b680 100644
--- a/src/adql/query/ADQLQuery.java
+++ b/src/adql/query/ADQLQuery.java
@@ -16,7 +16,7 @@ package adql.query;
  * 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-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -25,12 +25,20 @@ import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import adql.db.DBColumn;
+import adql.db.DBType;
+import adql.db.DBType.DBDatatype;
 import adql.db.DefaultDBColumn;
 import adql.parser.ADQLParser;
 import adql.parser.ParseException;
 import adql.query.from.FromContent;
 import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
+import adql.query.operand.function.DefaultUDF;
+import adql.query.operand.function.geometry.BoxFunction;
+import adql.query.operand.function.geometry.CircleFunction;
+import adql.query.operand.function.geometry.PointFunction;
+import adql.query.operand.function.geometry.PolygonFunction;
+import adql.query.operand.function.geometry.RegionFunction;
 import adql.search.ISearchHandler;
 
 /**
@@ -38,7 +46,7 @@ import adql.search.ISearchHandler;
  * <p>The resulting object of the {@link ADQLParser} is an object of this class.</p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.4 (06/2015)
+ * @version 1.4 (03/2016)
  */
 public class ADQLQuery implements ADQLObject {
 
@@ -311,19 +319,50 @@ public class ADQLQuery implements ADQLObject {
 					// Here, this error should not occur any more, since it must have been caught by the DBChecker!
 				}
 			}else{
+				// Create the DBColumn:
 				DBColumn col = null;
+				// ...whose the name will be set with the SELECT item's alias: 
 				if (item.hasAlias()){
 					if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null){
 						col = ((ADQLColumn)operand).getDBLink();
 						col = col.copy(col.getDBName(), item.getAlias(), col.getTable());
 					}else
 						col = new DefaultDBColumn(item.getAlias(), null);
-				}else{
+				}
+				// ...or whose the name will be the name of the SELECT item:
+				else{
 					if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null)
 						col = ((ADQLColumn)operand).getDBLink();
 					if (col == null)
 						col = new DefaultDBColumn(item.getName(), null);
 				}
+
+				/* For columns created by default (from functions and operations generally), 
+				 * set the adequate type if known: */
+				// CASE: Well-defined UDF
+				if (operand instanceof DefaultUDF && ((DefaultUDF)operand).getDefinition() != null){
+					DBType type = ((DefaultUDF)operand).getDefinition().returnType;
+					((DefaultDBColumn)col).setDatatype(type);
+				}
+				// CASE: Point type:
+				else if (operand instanceof PointFunction)
+					((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.POINT));
+				// CASE: Region type:
+				else if (operand instanceof RegionFunction || operand instanceof CircleFunction || operand instanceof BoxFunction || operand instanceof PolygonFunction)
+					((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.REGION));
+				// CASE: String and numeric types
+				else if (col instanceof DefaultDBColumn && col.getDatatype() == null && operand.isNumeric() != operand.isString()){
+					// CASE: String types
+					if (operand.isString())
+						((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.VARCHAR));
+					// CASE: Numeric types:
+					/* Note: a little special case here since a numeric could be a real, double, integer, or anything
+					 *       else and that we don't know precisely here. So we set the special UNKNOWN NUMERIC type. */
+					else
+						((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.UNKNOWN_NUMERIC));
+				}
+
+				// Add the new column to the list:
 				columns.add(col);
 			}
 		}
diff --git a/src/adql/query/operand/ADQLColumn.java b/src/adql/query/operand/ADQLColumn.java
index af8e9d5..5542f50 100644
--- a/src/adql/query/operand/ADQLColumn.java
+++ b/src/adql/query/operand/ADQLColumn.java
@@ -16,7 +16,7 @@ package adql.query.operand;
  * 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-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -32,7 +32,7 @@ import adql.query.from.ADQLTable;
  * Represents the complete (literal) reference to a column ({schema(s)}.{table}.{column}).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.4 (06/2015)
+ * @version 1.4 (03/2016)
  */
 public class ADQLColumn implements ADQLOperand, UnknownType {
 
@@ -469,17 +469,17 @@ public class ADQLColumn implements ADQLOperand, UnknownType {
 
 	@Override
 	public boolean isNumeric(){
-		return (dbLink == null || dbLink.getDatatype() == null || dbLink.getDatatype().isUnknown() || dbLink.getDatatype().isNumeric());
+		return (dbLink == null || dbLink.getDatatype() == null || dbLink.getDatatype().isNumeric() || dbLink.getDatatype().isUnknown());
 	}
 
 	@Override
 	public boolean isString(){
-		return (dbLink == null || dbLink.getDatatype() == null || dbLink.getDatatype().isUnknown() || dbLink.getDatatype().isString());
+		return (dbLink == null || dbLink.getDatatype() == null || dbLink.getDatatype().isString() || (dbLink.getDatatype().isUnknown() && !dbLink.getDatatype().isNumeric()));
 	}
 
 	@Override
 	public boolean isGeometry(){
-		return (dbLink == null || dbLink.getDatatype() == null || dbLink.getDatatype().isUnknown() || dbLink.getDatatype().isGeometry());
+		return (dbLink == null || dbLink.getDatatype() == null || dbLink.getDatatype().isGeometry() || (dbLink.getDatatype().isUnknown() && !dbLink.getDatatype().isNumeric()));
 	}
 
 	@Override
diff --git a/test/adql/TestADQLQuery.java b/test/adql/TestADQLQuery.java
index 54a704e..1ea715b 100644
--- a/test/adql/TestADQLQuery.java
+++ b/test/adql/TestADQLQuery.java
@@ -1,6 +1,8 @@
 package adql;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -9,6 +11,9 @@ import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 
+import adql.db.DBType;
+import adql.db.DBType.DBDatatype;
+import adql.db.FunctionDef;
 import adql.query.ADQLObject;
 import adql.query.ADQLOrder;
 import adql.query.ADQLQuery;
@@ -21,12 +26,23 @@ import adql.query.constraint.ComparisonOperator;
 import adql.query.constraint.ConstraintsGroup;
 import adql.query.from.ADQLTable;
 import adql.query.operand.ADQLColumn;
+import adql.query.operand.ADQLOperand;
 import adql.query.operand.Concatenation;
 import adql.query.operand.NumericConstant;
 import adql.query.operand.Operation;
 import adql.query.operand.OperationType;
 import adql.query.operand.StringConstant;
 import adql.query.operand.WrappedOperand;
+import adql.query.operand.function.DefaultUDF;
+import adql.query.operand.function.MathFunction;
+import adql.query.operand.function.MathFunctionType;
+import adql.query.operand.function.SQLFunction;
+import adql.query.operand.function.SQLFunctionType;
+import adql.query.operand.function.geometry.BoxFunction;
+import adql.query.operand.function.geometry.CircleFunction;
+import adql.query.operand.function.geometry.PointFunction;
+import adql.query.operand.function.geometry.PolygonFunction;
+import adql.query.operand.function.geometry.RegionFunction;
 import adql.search.IReplaceHandler;
 import adql.search.ISearchHandler;
 import adql.search.SearchColumnHandler;
@@ -124,4 +140,103 @@ public class TestADQLQuery {
 			assertEquals(expectedCol, results.next());
 		assertEquals("SELECT (O.nameObj || ' (' || NewTypeObj || ')') AS Nom objet , O.ra , O.dec\nFROM truc.ObsCore AS O\nWHERE ra/dec > 1 AND (NewTypeObj = 'Star' OR NewTypeObj LIKE 'Galaxy*')\nORDER BY 1 DESC", query.toADQL());
 	}
+
+	@Test
+	public void testTypeResultingColumns(){
+		ADQLQuery query = new ADQLQuery();
+		query.setFrom(new ADQLTable("foo"));
+		ClauseSelect select = new ClauseSelect();
+		query.setSelect(select);
+
+		// Test with a numeric constant:
+		select.add(new NumericConstant(2.3));
+		assertEquals(1, query.getResultingColumns().length);
+		assertEquals(DBDatatype.UNKNOWN_NUMERIC, query.getResultingColumns()[0].getDatatype().type);
+
+		// Test with a math operation:
+		select.clear();
+		select.add(new Operation(new Operation(new NumericConstant(2), OperationType.MULT, new NumericConstant(3.14)), OperationType.DIV, new NumericConstant(5)));
+		assertEquals(1, query.getResultingColumns().length);
+		assertEquals(DBDatatype.UNKNOWN_NUMERIC, query.getResultingColumns()[0].getDatatype().type);
+
+		// Test with a math function:
+		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){
+			ex.printStackTrace();
+			fail("The mathematical function SQRT is well defined. This error should have occurred.");
+		}
+
+		// Test with an aggregation function:
+		select.clear();
+		select.add(new SQLFunction(SQLFunctionType.SUM, new ADQLColumn("col1")));
+		assertEquals(1, query.getResultingColumns().length);
+		assertEquals(DBDatatype.UNKNOWN_NUMERIC, query.getResultingColumns()[0].getDatatype().type);
+
+		// Test with a string constant:
+		select.clear();
+		select.add(new StringConstant("blabla"));
+		assertEquals(1, query.getResultingColumns().length);
+		assertEquals(DBDatatype.VARCHAR, query.getResultingColumns()[0].getDatatype().type);
+
+		// Test with a concatenation:
+		select.clear();
+		Concatenation concat = new Concatenation();
+		concat.add(new StringConstant("super "));
+		concat.add(new ADQLColumn("foo", "col"));
+		select.add(concat);
+		assertEquals(1, query.getResultingColumns().length);
+		assertEquals(DBDatatype.VARCHAR, query.getResultingColumns()[0].getDatatype().type);
+
+		// Test with a POINT:
+		try{
+			select.clear();
+			select.add(new PointFunction(new StringConstant(""), new ADQLColumn("ra"), new ADQLColumn("dec")));
+			assertEquals(1, query.getResultingColumns().length);
+			assertEquals(DBDatatype.POINT, query.getResultingColumns()[0].getDatatype().type);
+		}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{
+			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)));
+			ADQLOperand[] points = new ADQLOperand[6];
+			points[0] = new ADQLColumn("point1");
+			points[1] = new ADQLColumn("point2");
+			points[2] = new ADQLColumn("point3");
+			points[3] = new ADQLColumn("point4");
+			points[4] = new ADQLColumn("point5");
+			points[5] = new ADQLColumn("point6");
+			select.add(new PolygonFunction(new StringConstant(""), points));
+			select.add(new RegionFunction(new StringConstant("CIRCLE '' ra dec 2.3")));
+			assertEquals(4, query.getResultingColumns().length);
+			for(int i = 0; i < 4; i++)
+				assertEquals(DBDatatype.REGION, query.getResultingColumns()[i].getDatatype().type);
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("The geometrical functions are well defined. This error should have occurred.");
+		}
+
+		// Test with a UDF having no definition:
+		select.clear();
+		select.add(new DefaultUDF("foo", new ADQLOperand[0]));
+		assertEquals(1, query.getResultingColumns().length);
+		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);
+
+	}
 }
diff --git a/test/adql/db/TestFunctionDef.java b/test/adql/db/TestFunctionDef.java
index e9ea725..db30b5c 100644
--- a/test/adql/db/TestFunctionDef.java
+++ b/test/adql/db/TestFunctionDef.java
@@ -10,6 +10,7 @@ import org.junit.Test;
 import adql.db.DBType.DBDatatype;
 import adql.db.FunctionDef.FunctionParam;
 import adql.parser.ParseException;
+import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
 import adql.query.operand.NumericConstant;
 import adql.query.operand.StringConstant;
@@ -62,6 +63,7 @@ public class TestFunctionDef {
 				case UNKNOWN:
 					assertFalse(new FunctionDef("foo", new DBType(type)).isNumeric);
 					break;
+				case UNKNOWN_NUMERIC:
 				default:
 					assertTrue(new FunctionDef("foo", new DBType(type)).isNumeric);
 			}
@@ -338,6 +340,18 @@ public class TestFunctionDef {
 			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))});
+		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})));
+		// 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})));
 	}
 
 }
diff --git a/test/adql/parser/UnknownTypes.java b/test/adql/parser/UnknownTypes.java
index 3ecd60d..5f311a5 100644
--- a/test/adql/parser/UnknownTypes.java
+++ b/test/adql/parser/UnknownTypes.java
@@ -70,7 +70,7 @@ public class UnknownTypes {
 
 	@Test
 	public void testForColumns(){
-		final String QUERY_TXT = "SELECT FOO(C1), FOO(C2), C1, C2, C3 FROM T1";
+		final String QUERY_TXT = "SELECT FOO(C1), FOO(C2), FOO(C4), C1, C2, C3, C4 FROM T1";
 
 		try{
 			// Create the parser:
@@ -81,6 +81,7 @@ public class UnknownTypes {
 			table1.addColumn(new DefaultDBColumn("C1", table1));
 			table1.addColumn(new DefaultDBColumn("C2", new DBType(DBDatatype.UNKNOWN), table1));
 			table1.addColumn(new DefaultDBColumn("C3", new DBType(DBDatatype.VARCHAR), table1));
+			table1.addColumn(new DefaultDBColumn("C4", new DBType(DBDatatype.UNKNOWN_NUMERIC), table1));
 			Collection<DBTable> tList = Arrays.asList(new DBTable[]{table1});
 
 			// Check the type of the column T1.C1:
@@ -98,6 +99,16 @@ public class UnknownTypes {
 			assertFalse(col.getDatatype().isGeometry());
 			assertEquals("UNKNOWN", col.getDatatype().toString());
 
+			// Check the type of the column T1.C4:
+			col = table1.getColumn("C4", true);
+			assertNotNull(col);
+			assertNotNull(col.getDatatype());
+			assertTrue(col.getDatatype().isUnknown());
+			assertTrue(col.getDatatype().isNumeric());
+			assertFalse(col.getDatatype().isString());
+			assertFalse(col.getDatatype().isGeometry());
+			assertEquals("UNKNOWN_NUMERIC", col.getDatatype().toString());
+
 			// Define a UDF, and allow all geometrical functions and coordinate systems:
 			FunctionDef udf1 = FunctionDef.parse("FOO(x INTEGER) -> INTEGER");
 			Collection<FunctionDef> udfList = Arrays.asList(new FunctionDef[]{udf1});
@@ -122,18 +133,26 @@ public class UnknownTypes {
 			assertTrue(pq.getSelect().get(1).getOperand().isNumeric());
 			assertFalse(pq.getSelect().get(1).getOperand().isString());
 			assertFalse(pq.getSelect().get(1).getOperand().isGeometry());
-			// isNumeric() = isString() = isGeometry() for C1
+			// isNumeric() = true for FOO(C4), but false for the others
 			assertTrue(pq.getSelect().get(2).getOperand().isNumeric());
-			assertTrue(pq.getSelect().get(2).getOperand().isString());
-			assertTrue(pq.getSelect().get(2).getOperand().isGeometry());
-			// isNumeric() = isString() = isGeometry() for C2
+			assertFalse(pq.getSelect().get(2).getOperand().isString());
+			assertFalse(pq.getSelect().get(2).getOperand().isGeometry());
+			// isNumeric() = isString() = isGeometry() for C1
 			assertTrue(pq.getSelect().get(3).getOperand().isNumeric());
 			assertTrue(pq.getSelect().get(3).getOperand().isString());
 			assertTrue(pq.getSelect().get(3).getOperand().isGeometry());
-			// isString() = true for C3, but false for the others
-			assertFalse(pq.getSelect().get(4).getOperand().isNumeric());
+			// isNumeric() = isString() = isGeometry() for C2
+			assertTrue(pq.getSelect().get(4).getOperand().isNumeric());
 			assertTrue(pq.getSelect().get(4).getOperand().isString());
-			assertFalse(pq.getSelect().get(4).getOperand().isGeometry());
+			assertTrue(pq.getSelect().get(4).getOperand().isGeometry());
+			// isString() = true for C3, but false for the others
+			assertFalse(pq.getSelect().get(5).getOperand().isNumeric());
+			assertTrue(pq.getSelect().get(5).getOperand().isString());
+			assertFalse(pq.getSelect().get(5).getOperand().isGeometry());
+			// isString() = true for C4, but false for the others
+			assertTrue(pq.getSelect().get(6).getOperand().isNumeric());
+			assertFalse(pq.getSelect().get(6).getOperand().isString());
+			assertFalse(pq.getSelect().get(6).getOperand().isGeometry());
 		}catch(Exception ex){
 			ex.printStackTrace(System.err);
 			fail("The construction, configuration and usage of the parser are correct. Nothing should have failed here. (see console for more details)");
-- 
GitLab