diff --git a/src/adql/db/DBType.java b/src/adql/db/DBType.java index 87f6c05c81674a98bed60cc17ec67a7c2947eb0a..18a09e440cddd90122bda187bc2c5a5c6628d111 100644 --- a/src/adql/db/DBType.java +++ b/src/adql/db/DBType.java @@ -16,7 +16,7 @@ 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 - Astronomisches Rechen Institut (ARI) + * Copyright 2014-2015 - Astronomisches Rechen Institut (ARI) */ /** @@ -32,7 +32,7 @@ package adql.db; * It is used to set the attribute type/datatype of this class.</p> * * @author Grégory Mantelet (ARI) - * @version 1.3 (10/2014) + * @version 1.4 (07/2015) * @since 1.3 */ public class DBType { @@ -41,11 +41,40 @@ public class DBType { * List of all datatypes declared in the IVOA recommendation of TAP (in the section UPLOAD). * * @author Grégory Mantelet (ARI) - * @version 1.3 (10/2014) + * @version 1.4 (06/2015) * @since 1.3 */ public static enum DBDatatype{ - SMALLINT, INTEGER, BIGINT, REAL, DOUBLE, BINARY, VARBINARY, CHAR, VARCHAR, BLOB, CLOB, TIMESTAMP, POINT, REGION; + SMALLINT, INTEGER, BIGINT, REAL, DOUBLE, BINARY, VARBINARY, CHAR, VARCHAR, BLOB, CLOB, TIMESTAMP, POINT, REGION, + /** @since 1.4 */ + UNKNOWN; + + /** String to return when {@link #toString()} is called. + * @since 1.4*/ + private String strExp = this.name(); + + @Override + public String toString(){ + return strExp; + } + + /** + * <p>This function lets define the name of the type as provided + * <b>ONLY FOR {@link #UNKNOWN} {@link DBDatatype}</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. + * </i></p> + * + * @param typeName User type name. + * + * @since 1.4 + */ + public void setCustomType(final String typeName){ + if (this == UNKNOWN && typeName != null && typeName.trim().length() > 0) + strExp = "?" + typeName.trim() + "?"; + } } /** Special value in case no length/size is specified. */ @@ -79,6 +108,23 @@ public class DBType { this.length = length; } + /** + * <p>Tells whether this type is a numeric.</p> + * + * <p><i>Concerned types: + * {@link DBDatatype#SMALLINT SMALLINT}, {@link DBDatatype#INTEGER INTEGER}, {@link DBDatatype#BIGINT BIGINT}, + * {@link DBDatatype#REAL REAL}, {@link DBDatatype#DOUBLE DOUBLE}, {@link DBDatatype#BINARY BINARY}, + * {@link DBDatatype#VARBINARY VARBINARY} and {@link DBDatatype#BLOB BLOB}. + * </i></p> + * + * <p><i><b>Important note</b>: + * Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything. + * That's why, this function will also returned <code>true</code> if the type is + * {@link DBDatatype#UNKNOWN UNKNOWN}. + * </i></p> + * + * @return <code>true</code> if this type is a numeric, <code>false</code> otherwise. + */ public boolean isNumeric(){ switch(type){ case SMALLINT: @@ -91,42 +137,121 @@ public class DBType { case BINARY: case VARBINARY: case BLOB: + case UNKNOWN: return true; default: return false; } } + /** + * <p>Tells whether this type is a list of bytes.</p> + * + * <p><i>Concerned types: + * {@link DBDatatype#BINARY BINARY}, {@link DBDatatype#VARBINARY VARBINARY} and {@link DBDatatype#BLOB BLOB}. + * </i></p> + * + * <p><i><b>Important note</b>: + * Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything. + * That's why, this function will also returned <code>true</code> if the type is + * {@link DBDatatype#UNKNOWN UNKNOWN}. + * </i></p> + * + * @return <code>true</code> if this type is a binary, <code>false</code> otherwise. + */ public boolean isBinary(){ switch(type){ case BINARY: case VARBINARY: case BLOB: + case UNKNOWN: return true; default: return false; } } + /** + * <p>Tells whether this type is about characters.</p> + * + * <p><i>Concerned types: + * {@link DBDatatype#CHAR CHAR}, {@link DBDatatype#VARCHAR VARCHAR}, {@link DBDatatype#CLOB CLOB} + * and {@link DBDatatype#TIMESTAMP TIMESTAMP}. + * </i></p> + * + * <p><i><b>Important note</b>: + * Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything. + * That's why, this function will also returned <code>true</code> if the type is + * {@link DBDatatype#UNKNOWN UNKNOWN}. + * </i></p> + * + * @return <code>true</code> if this type is a string, <code>false</code> otherwise. + */ public boolean isString(){ switch(type){ case CHAR: case VARCHAR: case CLOB: case TIMESTAMP: + case UNKNOWN: return true; default: return false; } } + /** + * <p>Tells whether this type is a geometrical region.</p> + * + * <p><i>Concerned types: + * {@link DBDatatype#POINT POINT} and {@link DBDatatype#REGION REGION}. + * </i></p> + * + * <p><i><b>Important note</b>: + * Since {@link DBDatatype#UNKNOWN UNKNOWN} is an unresolved type, it can potentially be anything. + * That's why, this function will also returned <code>true</code> if the type is + * {@link DBDatatype#UNKNOWN UNKNOWN}. + * </i></p> + * + * @return <code>true</code> if this type is a geometry, <code>false</code> otherwise. + */ public boolean isGeometry(){ - return (type == DBDatatype.POINT || type == DBDatatype.REGION); + return (type == DBDatatype.POINT || type == DBDatatype.REGION || type == DBDatatype.UNKNOWN); } + /** + * <p>Tell whether this type has been resolved or not.</p> + * + * <p><i>Concerned type: + * {@link DBDatatype#UNKNOWN UNKNOWN}. + * </i></p> + * + * @return <code>true</code> if this type has NOT been resolved, <code>false</code> otherwise. + * + * @since 1.4 + */ + public boolean isUnknown(){ + return type == DBDatatype.UNKNOWN; + } + + /** + * <p>Tell whether this {@link DBType} is compatible with the given one.</p> + * + * <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>. + * </p> + * + * @param t The type to compare to. + * + * @return <code>true</code> if this type is compatible with the given one, <code>false</code> otherwise. + */ public boolean isCompatible(final DBType t){ if (t == null) return false; + else if (isUnknown() || t.isUnknown()) + return true; else if (isBinary() == t.isBinary()) return (type == DBDatatype.BLOB && t.type == DBDatatype.BLOB) || (type != DBDatatype.BLOB && t.type != DBDatatype.BLOB); else if (isNumeric() == t.isNumeric()) diff --git a/src/adql/db/FunctionDef.java b/src/adql/db/FunctionDef.java index 82107d0a232a33d2e1d113c795304261f1bfda66..42e29a4edba960df837b06100fed60186fb05f5a 100644 --- a/src/adql/db/FunctionDef.java +++ b/src/adql/db/FunctionDef.java @@ -49,7 +49,7 @@ import adql.query.operand.function.UserDefinedFunction; * </p> * * @author Grégory Mantelet (ARI) - * @version 1.3 (02/2015) + * @version 1.4 (07/2015) * * @since 1.3 */ @@ -86,6 +86,14 @@ public class FunctionDef implements Comparable<FunctionDef> { protected final boolean isNumeric; /** Indicate whether the return type is a geometry. */ protected final boolean isGeometry; + /** Indicate whether the return type is an unknown type. + * <p><i><u>Note:</u> + * If <code>true</code>, {@link #isString}, {@link #isNumeric} + * and {@link #isGeometry} are <code>false</code>. Otherwise, + * at least one of these attributes is set to <code>true</code>. + * </i></p> + * @since 1.4 */ + protected final boolean isUnknown; /** Total number of parameters. */ public final int nbParams; @@ -122,7 +130,7 @@ public class FunctionDef implements Comparable<FunctionDef> { * <p>This definition is composed of two items: the name and the type of the parameter.</p> * * @author Grégory Mantelet (ARI) - * @version 1.3 (10/2014) + * @version 1.4 (07/2015) * @since 1.3 */ public static final class FunctionParam { @@ -135,15 +143,13 @@ public class FunctionDef implements Comparable<FunctionDef> { * Create a function parameter. * * @param paramName Name of the parameter to create. <i>MUST NOT be NULL</i> - * @param paramType Type of the parameter to create. <i>MUST NOT be NULL</i> + * @param paramType Type of the parameter to create. <i>If NULL, an {@link DBDatatype#UNKNOWN UNKNOWN} type will be created and set instead.</i> */ public FunctionParam(final String paramName, final DBType paramType){ if (paramName == null) throw new NullPointerException("Missing name! The function parameter can not be created."); - if (paramType == null) - throw new NullPointerException("Missing type! The function parameter can not be created."); this.name = paramName; - this.type = paramType; + this.type = (paramType == null) ? new DBType(DBDatatype.UNKNOWN) : paramType; } } @@ -193,13 +199,11 @@ public class FunctionDef implements Comparable<FunctionDef> { this.nbParams = (params == null) ? 0 : params.length; // Set the return type; - this.returnType = returnType; - if (returnType != null){ - isNumeric = returnType.isNumeric(); - isString = returnType.isString(); - isGeometry = returnType.isGeometry(); - }else - isNumeric = isString = isGeometry = false; + this.returnType = (returnType != null) ? returnType : new DBType(DBDatatype.UNKNOWN); + isUnknown = this.returnType.isUnknown(); + isNumeric = isUnknown || this.returnType.isNumeric(); + isString = isUnknown || this.returnType.isString(); + isGeometry = isUnknown || this.returnType.isGeometry(); // Serialize in Strings (serializedForm and compareForm) this function definition: StringBuffer bufSer = new StringBuffer(name), bufCmp = new StringBuffer(name.toLowerCase()); @@ -244,6 +248,20 @@ public class FunctionDef implements Comparable<FunctionDef> { return isGeometry; } + /** + * <p>Tell whether this function returns an unknown type.</p> + * + * <p> + * If this function returns <code>true</code>, {@link #isNumeric()}, {@link #isString()} and {@link #isGeometry()} + * <b>MUST ALL</b> return <code>false</code>. Otherwise, one of these 3 last functions MUST return <code>true</code>. + * </p> + * + * @return <i>true</i> if this function returns an unknown/unresolved/unsupported type, <i>false</i> otherwise. + */ + public final boolean isUnknown(){ + return isUnknown; + } + /** * Get the number of parameters required by this function. * @@ -357,8 +375,10 @@ public class FunctionDef implements Comparable<FunctionDef> { DBType returnType = null; if (m.group(3) != null){ returnType = parseType(m.group(5), (m.group(7) == null) ? DBType.NO_LENGTH : Integer.parseInt(m.group(7))); - if (returnType == null) - throw new ParseException("Unknown return type: \"" + m.group(4).trim() + "\"!"); + if (returnType == null){ + returnType = new DBType(DBDatatype.UNKNOWN); + returnType.type.setCustomType(m.group(4)); + } } // Get the parameters, if any: @@ -384,10 +404,11 @@ public class FunctionDef implements Comparable<FunctionDef> { paramType = parseType(m.group(2), (m.group(4) == null) ? DBType.NO_LENGTH : Integer.parseInt(m.group(4))); // ...build the parameter definition object: - if (paramType == null) - throw new ParseException("Unknown type for the parameter \"" + m.group(1) + "\": \"" + m.group(2) + ((m.group(3) == null) ? "" : m.group(3)) + "\"!"); - else - params[i] = new FunctionParam(m.group(1), paramType); + if (paramType == null){ + paramType = new DBType(DBDatatype.UNKNOWN); + paramType.type.setCustomType(m.group(2) + ((m.group(3) == null) ? "" : m.group(3))); + } + params[i] = new FunctionParam(m.group(1), paramType); }else // note: should never happen because we have already check the syntax of the whole parameters list before parsing each individual parameter. throw new ParseException("Wrong syntax for the " + (i + 1) + "-th parameter: \"" + paramsSplit[i].trim() + "\"! Expected syntax: \"(<regular_identifier> <type_name> (, <regular_identifier> <type_name>)*)\", where <regular_identifier>=\"[a-zA-Z]+[a-zA-Z0-9_]*\", <type_name> should be one of the types described in the UPLOAD section of the TAP documentation. Examples of good syntax: \"()\", \"(param INTEGER)\", \"(param1 INTEGER, param2 DOUBLE)\""); @@ -410,7 +431,8 @@ public class FunctionDef implements Comparable<FunctionDef> { * <i>Note: This length will be used only for binary (BINARY and VARBINARY) * and character (CHAR and VARCHAR) types.</i> * - * @return The object representation of the specified datatype. + * @return The object representation of the specified datatype + * or NULL if the specified datatype can not be resolved. */ private static DBType parseType(String datatype, int length){ if (datatype == null) @@ -519,7 +541,9 @@ public class FunctionDef implements Comparable<FunctionDef> { // If equals, compare the parameters' type: if (comp == 0){ for(int i = 0; comp == 0 && i < nbParams && i < fct.getNbParameters(); i++){ - if (params[i].type.isNumeric() == fct.getParameter(i).isNumeric()){ + if (fct.getParameter(i).isNumeric() && fct.getParameter(i).isString() && fct.getParameter(i).isGeometry()) + comp = 0; + else if (params[i].type.isNumeric() == fct.getParameter(i).isNumeric()){ if (params[i].type.isString() == fct.getParameter(i).isString()){ if (params[i].type.isGeometry() == fct.getParameter(i).isGeometry()) comp = 0; diff --git a/src/adql/translator/PgSphereTranslator.java b/src/adql/translator/PgSphereTranslator.java index 96c509c52744219b63c7560a82988cd06a72d720..6474ab34bea48921ee21b66dba4c853b8ac00732 100644 --- a/src/adql/translator/PgSphereTranslator.java +++ b/src/adql/translator/PgSphereTranslator.java @@ -16,7 +16,7 @@ package adql.translator; * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -48,7 +48,7 @@ import adql.query.operand.function.geometry.PolygonFunction; * The other functions are managed by {@link PostgreSQLTranslator}.</p> * * @author Grégory Mantelet (CDS;ARI) - * @version 1.3 (11/2014) + * @version 1.4 (07/2015) */ public class PgSphereTranslator extends PostgreSQLTranslator { @@ -202,7 +202,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) - return new DBType(DBDatatype.VARCHAR, DBType.NO_LENGTH); + return null; // Put the dbmsTypeName in lower case for the following comparisons: dbmsTypeName = dbmsTypeName.toLowerCase(); diff --git a/src/adql/translator/PostgreSQLTranslator.java b/src/adql/translator/PostgreSQLTranslator.java index 4b2d01395a3e049ff05a4901a0ce8b05624c76e9..9c500b35584efed7da1a4b1c4b35fa9a9c170fa0 100644 --- a/src/adql/translator/PostgreSQLTranslator.java +++ b/src/adql/translator/PostgreSQLTranslator.java @@ -16,7 +16,7 @@ package adql.translator; * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -50,7 +50,7 @@ import adql.query.operand.function.geometry.RegionFunction; * </i></p> * * @author Grégory Mantelet (CDS;ARI) - * @version 1.3 (11/2014) + * @version 1.4 (07/2015) * * @see PgSphereTranslator */ @@ -196,7 +196,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) - return new DBType(DBDatatype.VARCHAR, DBType.NO_LENGTH); + return null; // Put the dbmsTypeName in lower case for the following comparisons: dbmsTypeName = dbmsTypeName.toLowerCase(); @@ -247,7 +247,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { return new DBType(DBDatatype.TIMESTAMP); // Default: else - return new DBType(DBDatatype.VARCHAR, DBType.NO_LENGTH); + return null; } @Override diff --git a/src/tap/data/ResultSetTableIterator.java b/src/tap/data/ResultSetTableIterator.java index 9c906a15783082d45389b5da4ce4897ff8a67a8b..220d9b7cbd9f6bc0f1742612b7650fb5141202a0 100644 --- a/src/tap/data/ResultSetTableIterator.java +++ b/src/tap/data/ResultSetTableIterator.java @@ -16,7 +16,7 @@ package tap.data; * 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 - Astronomisches Rechen Institut (ARI) + * Copyright 2014-2015 - Astronomisches Rechen Institut (ARI) */ import java.sql.ResultSet; @@ -42,7 +42,7 @@ import adql.translator.JDBCTranslator; * </i></p> * * @author Grégory Mantelet (ARI) - * @version 2.0 (11/2014) + * @version 2.1 (07/2015) * @since 2.0 */ public class ResultSetTableIterator implements TableIterator { @@ -363,7 +363,7 @@ public class ResultSetTableIterator implements TableIterator { if (o instanceof Timestamp) o = ISO8601Format.format(((Timestamp)o).getTime()); // if the column value is a geometrical object, it must be serialized in STC-S: - else if (translator != null && colType.isGeometry()){ + else if (translator != null && colType != null && colType.isGeometry()){ Region region = translator.translateGeometryFromDB(o); if (region != null) o = region.toSTCS(); @@ -409,7 +409,8 @@ public class ResultSetTableIterator implements TableIterator { * @param dbmsType DBMS column data-type name. * @param dbms Lower-case string which indicates which DBMS the ResultSet is coming from. <i>note: MAY be NULL.</i> * - * @return The best suited {@link DBType} object. + * @return The best suited {@link DBType} object, + * or an {@link DBDatatype#UNKNOWN UNKNOWN} type if none can be found. * * @see JDBCTranslator#convertTypeFromDB(int, String, String, String[]) * @see #defaultTypeConversion(String, String[], String) @@ -417,7 +418,7 @@ public class ResultSetTableIterator implements TableIterator { protected DBType convertType(final int dbmsType, String dbmsTypeName, final String dbms) throws DataReadException{ // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) - return new DBType(DBDatatype.VARCHAR, DBType.NO_LENGTH); + return new DBType(DBDatatype.UNKNOWN); // Extract the type prefix and lower-case it: int startParamIndex = dbmsTypeName.indexOf('('), endParamIndex = dbmsTypeName.indexOf(')'); @@ -468,7 +469,8 @@ public class ResultSetTableIterator implements TableIterator { * @param params The eventual type parameters (e.g. char string length). * @param dbms The targeted DBMS. * - * @return The corresponding ADQL/TAP type. <i>NEVER NULL</i> + * @return The corresponding ADQL/TAP type. <i>NEVER NULL ; + * an {@link DBDatatype#UNKNOWN UNKNOWN} type is returned in case no match can be found.</i> */ protected final DBType defaultTypeConversion(final String dbmsTypeName, final String[] params, final String dbms){ // Get the length parameter (always in first position): @@ -495,7 +497,7 @@ public class ResultSetTableIterator implements TableIterator { return new DBType(DBDatatype.BLOB); // Default: else - return new DBType(DBDatatype.VARCHAR, DBType.NO_LENGTH); + return new DBType(DBDatatype.UNKNOWN); } // CASE: OTHER DBMS else{ @@ -537,7 +539,7 @@ public class ResultSetTableIterator implements TableIterator { return new DBType(DBDatatype.TIMESTAMP); // Default: else - return new DBType(DBDatatype.VARCHAR, DBType.NO_LENGTH); + return new DBType(DBDatatype.UNKNOWN); } } diff --git a/src/tap/data/VOTableIterator.java b/src/tap/data/VOTableIterator.java index 95d2d5611e2c3ac679cb2b355100ed0ed33b37df..277b5b024c69eb65ab47cd9e892f4d12dc069610 100644 --- a/src/tap/data/VOTableIterator.java +++ b/src/tap/data/VOTableIterator.java @@ -42,7 +42,7 @@ import adql.db.DBType; * <p>{@link #getColType()} will return TAP type based on the type declared in the VOTable metadata part.</p> * * @author Grégory Mantelet (ARI) - * @version 2.0 (04/2015) + * @version 2.1 (07/2015) * @since 2.0 */ public class VOTableIterator implements TableIterator { @@ -460,14 +460,14 @@ public class VOTableIterator implements TableIterator { * @param arraysize Attribute value of VOTable corresponding to the arraysize. * @param xtype Attribute value of VOTable corresponding to the xtype. * - * @return The resolved VOTable field type, or a CHAR(*) type if the specified type can not be resolved. + * @return The resolved VOTable field type. * - * @throws DataReadException If a field datatype is unknown. + * @throws DataReadException If a field datatype is unknown or missing. */ public static VotType resolveVotType(final String datatype, final String arraysize, final String xtype) throws DataReadException{ // If no datatype is specified, return immediately a CHAR(*) type: if (datatype == null || datatype.trim().length() == 0) - return new VotType(VotDatatype.CHAR, "*"); + throw new DataReadException("missing VOTable required field: \"datatype\"!"); // Identify the specified datatype: VotDatatype votdatatype; diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java index 06044ad0913c1fc05953966f97251f1016c885ac..f8dc32d10fb854bcc3716424b79ca7e869a228a5 100644 --- a/src/tap/db/JDBCConnection.java +++ b/src/tap/db/JDBCConnection.java @@ -784,7 +784,7 @@ public class JDBCConnection implements DBConnection { // ...build the column type: DBType type; if (tapDatatype == null) - type = new DBType(DBDatatype.VARCHAR); + type = new DBType(DBDatatype.UNKNOWN); else type = new DBType(tapDatatype, size); @@ -1960,7 +1960,7 @@ public class JDBCConnection implements DBConnection { * * @param datatype Column TAP type. * - * @return The corresponding DB type, or NULL if the given type is not managed or is NULL. + * @return The corresponding DB type, or VARCHAR if the given type is not managed or is NULL. */ protected String defaultTypeConversion(DBType datatype){ if (datatype == null) diff --git a/src/tap/formatter/JSONFormat.java b/src/tap/formatter/JSONFormat.java index 4e808896f81f8c0899436981b00480b4d1f23866..515e43a3e9f3dfd0b716f95d7d651ff945f97769 100644 --- a/src/tap/formatter/JSONFormat.java +++ b/src/tap/formatter/JSONFormat.java @@ -42,7 +42,7 @@ import adql.db.DBType.DBDatatype; * Format any given query (table) result into JSON. * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (04/2015) + * @version 2.1 (07/2015) */ public class JSONFormat implements OutputFormat { @@ -178,9 +178,12 @@ public class JSONFormat implements OutputFormat { * @return The most appropriate metadata. */ protected TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ - if (typeFromQuery != null && typeFromQuery instanceof TAPColumn) - return (TAPColumn)typeFromQuery; - else if (typeFromResult != null){ + if (typeFromQuery != null && typeFromQuery instanceof TAPColumn){ + TAPColumn colMeta = (TAPColumn)typeFromQuery; + if (colMeta.getDatatype().type == DBDatatype.UNKNOWN && typeFromResult != null && typeFromResult.getDatatype().type != DBDatatype.UNKNOWN) + colMeta.setDatatype(typeFromResult.getDatatype()); + return colMeta; + }else if (typeFromResult != null){ if (typeFromQuery != null) return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null); else diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index f38b17eba48ce2bbc695c8771609d77dc7d77475..6156d212f52a31cf7c2e58f4916f4218608b4d66 100644 --- a/src/tap/formatter/VOTableFormat.java +++ b/src/tap/formatter/VOTableFormat.java @@ -83,7 +83,7 @@ import adql.db.DBType.DBDatatype; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (04/2015) + * @version 2.1 (07/2015) */ public class VOTableFormat implements OutputFormat { @@ -462,9 +462,12 @@ public class VOTableFormat implements OutputFormat { * @return The most appropriate metadata. */ protected static final TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ - if (typeFromQuery != null && typeFromQuery instanceof TAPColumn) - return (TAPColumn)typeFromQuery; - else if (typeFromResult != null){ + if (typeFromQuery != null && typeFromQuery instanceof TAPColumn){ + TAPColumn colMeta = (TAPColumn)typeFromQuery; + if (colMeta.getDatatype().type == DBDatatype.UNKNOWN && typeFromResult != null && typeFromResult.getDatatype().type != DBDatatype.UNKNOWN) + colMeta.setDatatype(typeFromResult.getDatatype()); + return colMeta; + }else if (typeFromResult != null){ if (typeFromQuery != null) return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null); else diff --git a/src/tap/metadata/TAPColumn.java b/src/tap/metadata/TAPColumn.java index 30d4d1d7492838bef1c1d3ea472b88d96bf0bd84..289dbe938969247a00f4a3f4aac05107b811eff3 100644 --- a/src/tap/metadata/TAPColumn.java +++ b/src/tap/metadata/TAPColumn.java @@ -16,7 +16,7 @@ package tap.metadata; * 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 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -67,7 +67,7 @@ import adql.db.DBType.DBDatatype; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (02/2015) + * @version 2.1 (07/2015) */ public class TAPColumn implements DBColumn { @@ -102,7 +102,7 @@ public class TAPColumn implements DBColumn { /** Type of this column. * <i>Note: Standard TAP column field ; CAN'T be NULL.</i> */ - private DBType datatype = new DBType(DBDatatype.VARCHAR); + private DBType datatype = new DBType(DBDatatype.UNKNOWN); /** Flag indicating whether this column is one of those that should be returned by default. * <i>Note: Standard TAP column field ; FALSE by default.</i> */ @@ -494,7 +494,7 @@ public class TAPColumn implements DBColumn { * <p>Set the type of the column's values.</p> * * <p><i>Note: - * The datatype won't be changed, if the given type is NULL. + * If the given type is NULL, a {@link DBDatatype#UNKNOWN UNKNOWN} type will be set instead. * </i></p> * * @param type Its new datatype. @@ -502,6 +502,8 @@ public class TAPColumn implements DBColumn { public final void setDatatype(final DBType type){ if (type != null) datatype = type; + else + datatype = new DBType(DBDatatype.UNKNOWN); } /** diff --git a/test/adql/db/TestFunctionDef.java b/test/adql/db/TestFunctionDef.java index c3d738a07aa0185136ab5199b98c915ec51ae1ed..20ae1da3ff4c87bd0b9fc48ad7cad88e7e5b3121 100644 --- a/test/adql/db/TestFunctionDef.java +++ b/test/adql/db/TestFunctionDef.java @@ -27,6 +27,7 @@ public class TestFunctionDef { case VARCHAR: case TIMESTAMP: case CLOB: + case UNKNOWN: assertTrue(new FunctionDef("foo", new DBType(type)).isString); break; default: @@ -41,6 +42,7 @@ public class TestFunctionDef { switch(type){ case POINT: case REGION: + case UNKNOWN: assertTrue(new FunctionDef("foo", new DBType(type)).isGeometry); break; default: @@ -170,18 +172,26 @@ public class TestFunctionDef { assertEquals("Wrong syntax for the 1-th parameter: \"param\"! Expected syntax: \"(<regular_identifier> <type_name> (, <regular_identifier> <type_name>)*)\", where <regular_identifier>=\"[a-zA-Z]+[a-zA-Z0-9_]*\", <type_name> should be one of the types described in the UPLOAD section of the TAP documentation. Examples of good syntax: \"()\", \"(param INTEGER)\", \"(param1 INTEGER, param2 DOUBLE)\"", ex.getMessage()); } try{ - FunctionDef.parse("foo()->aType"); - fail("Wrong (return) type!"); + FunctionDef fct = FunctionDef.parse("foo()->aType"); + assertTrue(fct.isUnknown); + assertTrue(fct.isString); + assertTrue(fct.isNumeric); + assertTrue(fct.isGeometry); + assertEquals("?aType?", fct.returnType.type.toString()); }catch(Exception ex){ - assertTrue(ex instanceof ParseException); - assertEquals("Unknown return type: \"aType\"!", ex.getMessage()); + ex.printStackTrace(System.err); + fail("Unknown types MUST be allowed!"); } try{ - FunctionDef.parse("foo()->aType(10)"); - fail("Wrong (return) type!"); + FunctionDef fct = FunctionDef.parse("foo()->aType(10)"); + assertTrue(fct.isUnknown); + assertTrue(fct.isString); + assertTrue(fct.isNumeric); + assertTrue(fct.isGeometry); + assertEquals("?aType(10)?", fct.returnType.type.toString()); }catch(Exception ex){ - assertTrue(ex instanceof ParseException); - assertEquals("Unknown return type: \"aType(10)\"!", ex.getMessage()); + ex.printStackTrace(System.err); + fail("Unknown types MUST be allowed!"); } try{ FunctionDef.parse("foo() -> "); @@ -205,18 +215,26 @@ public class TestFunctionDef { assertEquals(WRONG_PARAM_SYNTAX, ex.getMessage()); } try{ - FunctionDef.parse("foo(param1 aType)"); - fail("Wrong parameter type!"); + FunctionDef fct = FunctionDef.parse("foo(param1 aType)"); + assertTrue(fct.getParam(0).type.isUnknown()); + assertTrue(fct.getParam(0).type.isString()); + assertTrue(fct.getParam(0).type.isNumeric()); + assertTrue(fct.getParam(0).type.isGeometry()); + assertEquals("?aType?", fct.getParam(0).type.toString()); }catch(Exception ex){ - assertTrue(ex instanceof ParseException); - assertEquals("Unknown type for the parameter \"param1\": \"aType\"!", ex.getMessage()); + ex.printStackTrace(System.err); + fail("Unknown types MUST be allowed!"); } try{ - FunctionDef.parse("foo(param1 aType(10))"); - fail("Wrong parameter type!"); + FunctionDef fct = FunctionDef.parse("foo(param1 aType(10))"); + assertTrue(fct.getParam(0).type.isUnknown()); + assertTrue(fct.getParam(0).type.isString()); + assertTrue(fct.getParam(0).type.isNumeric()); + assertTrue(fct.getParam(0).type.isGeometry()); + assertEquals("?aType(10)?", fct.getParam(0).type.toString()); }catch(Exception ex){ - assertTrue(ex instanceof ParseException); - assertEquals("Unknown type for the parameter \"param1\": \"aType(10)\"!", ex.getMessage()); + ex.printStackTrace(System.err); + fail("Unknown types MUST be allowed!"); } } diff --git a/test/adql/parser/UnknownTypes.java b/test/adql/parser/UnknownTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..0ba4e0baeb79508a7dea531ed50836aa9d5dc2b0 --- /dev/null +++ b/test/adql/parser/UnknownTypes.java @@ -0,0 +1,118 @@ +package adql.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import adql.db.DBChecker; +import adql.db.DBColumn; +import adql.db.DBTable; +import adql.db.DBType; +import adql.db.DBType.DBDatatype; +import adql.db.DefaultDBColumn; +import adql.db.DefaultDBTable; +import adql.db.FunctionDef; +import adql.query.ADQLQuery; + +public class UnknownTypes { + + @BeforeClass + public static void setUpBeforeClass() throws Exception{} + + @AfterClass + public static void tearDownAfterClass() throws Exception{} + + @Before + public void setUp() throws Exception{} + + @After + public void tearDown() throws Exception{} + + public void testForFctDef(){ + // Test with the return type: + try{ + FunctionDef fct = FunctionDef.parse("foo()->aType"); + assertTrue(fct.isUnknown()); + assertTrue(fct.isString()); + assertTrue(fct.isNumeric()); + assertTrue(fct.isGeometry()); + assertEquals("?aType?", fct.returnType.type.toString()); + }catch(Exception ex){ + ex.printStackTrace(System.err); + fail("Unknown types MUST be allowed!"); + } + + // Test with a parameter type: + try{ + FunctionDef fct = FunctionDef.parse("foo(param1 aType)"); + assertTrue(fct.getParam(0).type.isUnknown()); + assertTrue(fct.getParam(0).type.isString()); + assertTrue(fct.getParam(0).type.isNumeric()); + assertTrue(fct.getParam(0).type.isGeometry()); + assertEquals("?aType?", fct.getParam(0).type.toString()); + }catch(Exception ex){ + ex.printStackTrace(System.err); + fail("Unknown types MUST be allowed!"); + } + } + + @Test + public void testForColumns(){ + final String QUERY_TXT = "SELECT FOO(C1), FOO(C2) FROM T1"; + + try{ + // Create the parser: + ADQLParser parser = new ADQLParser(); + + // Create table/column metadata: + DefaultDBTable table1 = new DefaultDBTable("T1"); + table1.addColumn(new DefaultDBColumn("C1", table1)); + table1.addColumn(new DefaultDBColumn("C2", new DBType(DBDatatype.UNKNOWN), table1)); + Collection<DBTable> tList = Arrays.asList(new DBTable[]{table1}); + + // Check the type of the column T1.C1: + DBColumn col = table1.getColumn("C1", true); + assertNotNull(col); + assertNull(col.getDatatype()); + + // Check the type of the column T1.C2: + col = table1.getColumn("C2", true); + assertNotNull(col); + assertNotNull(col.getDatatype()); + assertTrue(col.getDatatype().isUnknown()); + assertTrue(col.getDatatype().isNumeric()); + assertTrue(col.getDatatype().isString()); + assertTrue(col.getDatatype().isGeometry()); + assertEquals("UNKNOWN", 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}); + Collection<String> geoList = null; + Collection<String> csList = null; + + // Create the Query checker: + QueryChecker checker = new DBChecker(tList, udfList, geoList, csList); + + // Parse the query: + ADQLQuery pq = parser.parseQuery(QUERY_TXT); + + // Check the parsed query: + checker.check(pq); + }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)"); + } + } +}