diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java index 03abe421fbc50f01c85685ac6d5c732b4746eb44..fb78f4c15279f0f896da4fb039398402a616aa9e 100644 --- a/src/adql/parser/ADQLQueryFactory.java +++ b/src/adql/parser/ADQLQueryFactory.java @@ -64,7 +64,7 @@ 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.UnitConversionFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; @@ -319,8 +319,8 @@ public class ADQLQueryFactory { } /** @since 2.0 */ - public UnitConversionFunction createUnitConversionFunction(final ADQLOperand value, final ADQLOperand targetUnit) throws Exception { - return new UnitConversionFunction(value, targetUnit); + public InUnitFunction createInUnitFunction(final ADQLOperand value, final ADQLOperand targetUnit) throws Exception { + return new InUnitFunction(value, targetUnit); } /** diff --git a/src/adql/parser/feature/FeatureSet.java b/src/adql/parser/feature/FeatureSet.java index 70ac87c5934f17d2af376daf1990841939a56b88..e01d6e319175a86d8fd43c71909788884d35d674 100644 --- a/src/adql/parser/feature/FeatureSet.java +++ b/src/adql/parser/feature/FeatureSet.java @@ -32,7 +32,7 @@ import adql.query.ClauseOffset; import adql.query.constraint.ComparisonOperator; import adql.query.operand.BitNotOperand; import adql.query.operand.OperationType; -import adql.query.operand.function.UnitConversionFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -597,7 +597,7 @@ public class FeatureSet implements Iterable<LanguageFeature> { * <p><i><b>Important note:</b> * All of them must be optional and must have a type. * </i></p> */ - static LanguageFeature[] availableFeatures = new LanguageFeature[]{ UnitConversionFunction.FEATURE, BitNotOperand.FEATURE, OperationType.BIT_AND.getFeatureDescription(), OperationType.BIT_OR.getFeatureDescription(), OperationType.BIT_XOR.getFeatureDescription(), ClauseOffset.FEATURE, ComparisonOperator.ILIKE.getFeatureDescription(), LowerFunction.FEATURE, AreaFunction.FEATURE, BoxFunction.FEATURE, CentroidFunction.FEATURE, CircleFunction.FEATURE, ContainsFunction.FEATURE, ExtractCoord.FEATURE_COORD1, ExtractCoord.FEATURE_COORD2, ExtractCoordSys.FEATURE, DistanceFunction.FEATURE, IntersectsFunction.FEATURE, PointFunction.FEATURE, PolygonFunction.FEATURE, RegionFunction.FEATURE }; + static LanguageFeature[] availableFeatures = new LanguageFeature[]{ InUnitFunction.FEATURE, BitNotOperand.FEATURE, OperationType.BIT_AND.getFeatureDescription(), OperationType.BIT_OR.getFeatureDescription(), OperationType.BIT_XOR.getFeatureDescription(), ClauseOffset.FEATURE, ComparisonOperator.ILIKE.getFeatureDescription(), LowerFunction.FEATURE, AreaFunction.FEATURE, BoxFunction.FEATURE, CentroidFunction.FEATURE, CircleFunction.FEATURE, ContainsFunction.FEATURE, ExtractCoord.FEATURE_COORD1, ExtractCoord.FEATURE_COORD2, ExtractCoordSys.FEATURE, DistanceFunction.FEATURE, IntersectsFunction.FEATURE, PointFunction.FEATURE, PolygonFunction.FEATURE, RegionFunction.FEATURE }; /** * List all available language features. diff --git a/src/adql/parser/feature/default_features.md b/src/adql/parser/feature/default_features.md new file mode 100644 index 0000000000000000000000000000000000000000..80f7b4a6434df30dc7e0f94fd4aa961b00cf4dbc --- /dev/null +++ b/src/adql/parser/feature/default_features.md @@ -0,0 +1,36 @@ + +## Default features set in ADQLParser + +At its initialization, ADQLParser sets a list of supported optional language +features. By default, this list is as permissive as possible: everything +compatible with the implemented ADQL version is supported, and any UDF (even if +not declared) is allowed. + +Of course, this list can be customized after creation of the parser. +_See the javadoc of ADQLParser for more technical details._ + +## Supported features provided by an ADQLTranslator + +Databases are not supported guaranteed to support all optional features +introduced by ADQL. + +For this reason, an appropriate list of supported optional features is provided +in each implementation of ADQLTranslator: use the function +ADQLTranslator.getSupportedFeatures() to discover these features. + +Here is a sum-up of supported features for each implemented translator: + +| Feature | MySQL | MS-SQL Server | PostgreSQL | PgSphere | +| bitwise operations | X | X | X | X | +| LOWER | X | X | X | X | +| geometries | | | | X | +| ILIKE | | | X | X | +| IN_UNIT | | | | | + + +**Note about `ILIKE`:** In MySQL and MS-SQLServer, the case sensitiveness of +string comparison is determined by the collation of the compared strings. +ADQL-Lib could translate the ADQL's `o1 ILIKE o2` into +`LOWER(o1) LIKE LOWER(o2)` but in such case, the use of index on the column o1 +would fail. That's why nothing is done by default in ADQL-Lib. Whether or not +search in a column must be case sensitive or not is completely DB dependant. diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj index 9567045d11061f0899b5881ca73e24744e0a7d47..53bb5f81709aec53a6807f83201f9c97259d292b 100644 --- a/src/adql/parser/grammar/adqlGrammar201.jj +++ b/src/adql/parser/grammar/adqlGrammar201.jj @@ -1504,16 +1504,16 @@ ADQLFunction NumericFunction(): {ADQLFunction fct;} { (fct=MathFunction() | fct=TrigFunction() | fct=GeometryFunction() - | fct=UnitConversionFunction() + | fct=InUnitFunction() | fct=UserDefinedFunction() { ((UserDefinedFunction)fct).setExpectedType('N'); }) {return fct;} } -UnitConversionFunction UnitConversionFunction() : { Token start, end; ADQLOperand value, destUnit; } { +InUnitFunction InUnitFunction() : { Token start, end; ADQLOperand value, destUnit; } { start=<IN_UNIT> <LEFT_PAR> value=NumericExpression() <COMMA> destUnit=StringExpression() end=<RIGHT_PAR> { try { - UnitConversionFunction fct = queryFactory.createUnitConversionFunction(value, destUnit); + InUnitFunction fct = queryFactory.createInUnitFunction(value, destUnit); fct.setPosition(new TextPosition(start, end)); return fct; }catch(Exception ex) { diff --git a/src/adql/query/operand/function/UnitConversionFunction.java b/src/adql/query/operand/function/InUnitFunction.java similarity index 95% rename from src/adql/query/operand/function/UnitConversionFunction.java rename to src/adql/query/operand/function/InUnitFunction.java index 00bbac99e7114d76e3e7c2a1991537fbc7fa94f7..c40a6df7eea29bfbd09f0b6f8b6277006cbd6de4 100644 --- a/src/adql/query/operand/function/UnitConversionFunction.java +++ b/src/adql/query/operand/function/InUnitFunction.java @@ -37,7 +37,7 @@ import adql.query.operand.ADQLOperand; * @version 2.0 (08/2019) * @since 2.0 */ -public class UnitConversionFunction extends ADQLFunction { +public class InUnitFunction extends ADQLFunction { /** Description of this ADQL Feature. */ public static final LanguageFeature FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_UNIT, "IN_UNIT", true, "Convert the given value (1st argument) into the given VO-Unit (2nd argument)."); @@ -61,7 +61,7 @@ public class UnitConversionFunction extends ADQLFunction { * @throws IllegalArgumentException If the 1st operand is not a numeric, * or if the 2nd is not a string. */ - public UnitConversionFunction(final ADQLOperand value, final ADQLOperand targetUnit) throws NullPointerException, IllegalArgumentException { + public InUnitFunction(final ADQLOperand value, final ADQLOperand targetUnit) throws NullPointerException, IllegalArgumentException { setValue(value); setTargetUnit(targetUnit); } @@ -147,7 +147,7 @@ public class UnitConversionFunction extends ADQLFunction { @Override public ADQLObject getCopy() throws Exception { - return new UnitConversionFunction((ADQLOperand)value.getCopy(), (ADQLOperand)targetUnit.getCopy()); + return new InUnitFunction((ADQLOperand)value.getCopy(), (ADQLOperand)targetUnit.getCopy()); } @Override diff --git a/src/adql/translator/ADQLTranslator.java b/src/adql/translator/ADQLTranslator.java index 2455ace569b9fcddfeed1804cea4af6eebacc35c..9b0c651d2c40b07b6ae5c2b64f21d2b93587685e 100644 --- a/src/adql/translator/ADQLTranslator.java +++ b/src/adql/translator/ADQLTranslator.java @@ -1,5 +1,7 @@ package adql.translator; +import adql.parser.feature.FeatureSet; + /* * This file is part of ADQLLibrary. * @@ -51,7 +53,7 @@ import adql.query.operand.WrappedOperand; import adql.query.operand.function.ADQLFunction; import adql.query.operand.function.MathFunction; import adql.query.operand.function.SQLFunction; -import adql.query.operand.function.UnitConversionFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; @@ -79,6 +81,22 @@ import adql.query.operand.function.string.LowerFunction; */ public interface ADQLTranslator { + /** + * Get all features that are fully supported by this translator. + * + * <p><i><b>Note:</b> + * If NULL is returned, the default list of supported features should be + * used instead. This default list depends on the ADQL version and + * is set in an {@link adql.parser.ADQLParser ADQLParser} instance when no + * feature set is specified. + * </i></p> + * + * @return All features supported by this translator. + * + * @since 2.0 + */ + public FeatureSet getSupportedFeatures(); + public String translate(ADQLObject obj) throws TranslationException; public String translate(ADQLQuery query) throws TranslationException; @@ -153,7 +171,7 @@ public interface ADQLTranslator { public String translate(LowerFunction fct) throws TranslationException; /** @since 2.0 */ - public String translate(UnitConversionFunction fct) throws TranslationException; + public String translate(InUnitFunction fct) throws TranslationException; /* ***** GEOMETRICAL FUNCTIONS ***** */ public String translate(GeometryFunction fct) throws TranslationException; diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java index 99fcd3c54c25ebcc5396e8731285cc00b530c881..7042931cbc3f23a49e533446c08fc3a542ca0191 100644 --- a/src/adql/translator/JDBCTranslator.java +++ b/src/adql/translator/JDBCTranslator.java @@ -63,7 +63,7 @@ import adql.query.operand.function.ADQLFunction; import adql.query.operand.function.MathFunction; import adql.query.operand.function.SQLFunction; import adql.query.operand.function.SQLFunctionType; -import adql.query.operand.function.UnitConversionFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; @@ -837,8 +837,8 @@ public abstract class JDBCTranslator implements ADQLTranslator { return translate((UserDefinedFunction)fct); else if (fct instanceof LowerFunction) return translate((LowerFunction)fct); - else if (fct instanceof UnitConversionFunction) - return translate((UnitConversionFunction)fct); + else if (fct instanceof InUnitFunction) + return translate((InUnitFunction)fct); else return getDefaultADQLFunction(fct); } diff --git a/src/adql/translator/MySQLTranslator.java b/src/adql/translator/MySQLTranslator.java index 75a2438057ee66b657fe5c3d022c750b35df821c..ac80f4538d7f605844804d55f31e76e8edc11cd5 100644 --- a/src/adql/translator/MySQLTranslator.java +++ b/src/adql/translator/MySQLTranslator.java @@ -23,13 +23,16 @@ package adql.translator; import adql.db.DBType; import adql.db.DBType.DBDatatype; import adql.db.STCS.Region; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.IdentifierField; import adql.query.constraint.Comparison; +import adql.query.constraint.ComparisonOperator; import adql.query.operand.ADQLOperand; import adql.query.operand.Concatenation; import adql.query.operand.Operation; -import adql.query.operand.function.UnitConversionFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -47,12 +50,19 @@ import adql.query.operand.function.geometry.RegionFunction; * Translates all ADQL objects into an SQL interrogation query designed for * MySQL. * - * <p><i><b>Important</b>: - * The geometrical functions are translated exactly as in ADQL. + * <p><i><b>Important note 1:</b> + * The geometrical functions and IN_UNIT are translated exactly as in ADQL. * You will probably need to extend this translator to correctly manage the * geometrical functions. * </i></p> * + * <p><i><b>Important note 2:</b> + * If new optional features are supported in an extension of this translator, + * they should be visible in {@link #getSupportedFeatures()}. To customize this + * list, you must overwrite {@link #initSupportedFeatures()} and update in + * there the attribute {@link #supportedFeatures}. + * </i></p> + * * @author Grégory Mantelet (ARI;CDS) * @version 2.0 (08/2019) * @since 1.4 @@ -74,6 +84,14 @@ public class MySQLTranslator extends JDBCTranslator { */ protected byte caseSensitivity = 0x00; + /** List of all optional features supported by this translator. + * <p><i><b>Note:</b> + * This list can be customized by extending this translator and then + * overwriting {@link #initSupportedFeatures()}. + * </i></p> + * @since 2.0 */ + protected final FeatureSet supportedFeatures = new FeatureSet(); + /** * Build a MySQLTranslator which always translates in SQL all identifiers * (schema, table and column) in a case sensitive manner ; in other words, @@ -82,6 +100,7 @@ public class MySQLTranslator extends JDBCTranslator { */ public MySQLTranslator() { caseSensitivity = 0x0F; + initSupportedFeatures(); } /** @@ -97,6 +116,7 @@ public class MySQLTranslator extends JDBCTranslator { */ public MySQLTranslator(final boolean allCaseSensitive) { caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; + initSupportedFeatures(); } /** @@ -117,6 +137,40 @@ public class MySQLTranslator extends JDBCTranslator { caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema); caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table); caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column); + initSupportedFeatures(); + } + + /** + * Initialize the list of optional features supported by this translator. + * + * <p> + * By default, all optional features are supported except the following: + * </p> + * <ul> + * <li>All geometric functions,</li> + * <li>ILIKE,</li> + * <li>and IN_UNIT</li> + * </ul> + * + * @since 2.0 + */ + protected void initSupportedFeatures() { + // Any UDF allowed: + supportedFeatures.allowAnyUdf(true); + + // Support all features... + supportedFeatures.supportAll(); + // ...except all geometries: + supportedFeatures.unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + // ...except ILIKE: + supportedFeatures.unsupport(ComparisonOperator.ILIKE.getFeatureDescription()); + // ...except IN_UNIT: + supportedFeatures.unsupport(InUnitFunction.FEATURE); + } + + @Override + public final FeatureSet getSupportedFeatures() { + return supportedFeatures; } @Override @@ -179,7 +233,7 @@ public class MySQLTranslator extends JDBCTranslator { } @Override - public String translate(final UnitConversionFunction fct) throws TranslationException { + public String translate(final InUnitFunction fct) throws TranslationException { return getDefaultADQLFunction(fct); } diff --git a/src/adql/translator/PgSphereTranslator.java b/src/adql/translator/PgSphereTranslator.java index ef8cfdb7659bac036c08f2783731cd91e81b2332..360b57c2fa67c9547d08c2399a45406839e3bf0c 100644 --- a/src/adql/translator/PgSphereTranslator.java +++ b/src/adql/translator/PgSphereTranslator.java @@ -28,6 +28,7 @@ import org.postgresql.util.PGobject; import adql.db.DBType; import adql.db.DBType.DBDatatype; import adql.db.STCS.Region; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.TextPosition; import adql.query.constraint.Comparison; @@ -107,6 +108,15 @@ public class PgSphereTranslator extends PostgreSQLTranslator { super(catalog, schema, table, column); } + @Override + protected void initSupportedFeatures() { + // All features natively supported in PostgreSQL: + super.initSupportedFeatures(); + + // And thanks to PgSphere, all geometries are now supported: + supportedFeatures.supportAll(LanguageFeature.TYPE_ADQL_GEO); + } + @Override public String translate(PointFunction point) throws TranslationException { StringBuffer str = new StringBuffer("spoint("); diff --git a/src/adql/translator/PostgreSQLTranslator.java b/src/adql/translator/PostgreSQLTranslator.java index 26687a97334152ba5eab5c1ce90e731642c203eb..46d7a1a6deff668b446193b392c0839bb8fc14a6 100644 --- a/src/adql/translator/PostgreSQLTranslator.java +++ b/src/adql/translator/PostgreSQLTranslator.java @@ -23,14 +23,16 @@ package adql.translator; import adql.db.DBType; import adql.db.DBType.DBDatatype; import adql.db.STCS.Region; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.IdentifierField; import adql.query.operand.Operation; import adql.query.operand.OperationType; import adql.query.operand.StringConstant; import adql.query.operand.function.ADQLFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.MathFunction; -import adql.query.operand.function.UnitConversionFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -59,13 +61,20 @@ import adql.query.operand.function.geometry.RegionFunction; * case there is no interest of these geometrical functions. * </p> * - * <p><i><b>Important</b>: - * The geometrical functions are translated exactly as in ADQL. + * <p><i><b>Important note 1:</b> + * The geometrical functions and IN_UNIT are translated exactly as in ADQL. * You will probably need to extend this translator to correctly manage the * geometrical functions. An extension is already available for PgSphere: * {@link PgSphereTranslator}. * </i></p> * + * <p><i><b>Important note 2:</b> + * If new optional features are supported in an extension of this translator, + * they should be visible in {@link #getSupportedFeatures()}. To customize this + * list, you must overwrite {@link #initSupportedFeatures()} and update in + * there the attribute {@link #supportedFeatures}. + * </i></p> + * * @author Grégory Mantelet (CDS;ARI) * @version 2.0 (08/2019) * @@ -82,12 +91,21 @@ public class PostgreSQLTranslator extends JDBCTranslator { */ protected byte caseSensitivity = 0x00; + /** List of all optional features supported by this translator. + * <p><i><b>Note:</b> + * This list can be customized by extending this translator and then + * overwriting {@link #initSupportedFeatures()}. + * </i></p> + * @since 2.0 */ + protected final FeatureSet supportedFeatures = new FeatureSet(); + /** * Builds a PostgreSQLTranslator which always translates in SQL all identifiers (schema, table and column) in a case sensitive manner ; * in other words, schema, table and column names will be surrounded by double quotes in the SQL translation. */ public PostgreSQLTranslator() { caseSensitivity = 0x0F; + initSupportedFeatures(); } /** @@ -98,6 +116,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { */ public PostgreSQLTranslator(final boolean allCaseSensitive) { caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; + initSupportedFeatures(); } /** @@ -113,6 +132,38 @@ public class PostgreSQLTranslator extends JDBCTranslator { caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema); caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table); caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column); + initSupportedFeatures(); + } + + /** + * Initialize the list of optional features supported by this translator. + * + * <p> + * By default, all optional features are supported except the following: + * </p> + * <ul> + * <li>All geometric functions,</li> + * <li>ILIKE,</li> + * <li>and IN_UNIT</li> + * </ul> + * + * @since 2.0 + */ + protected void initSupportedFeatures() { + // Any UDF allowed: + supportedFeatures.allowAnyUdf(true); + + // Support all features... + supportedFeatures.supportAll(); + // ...except all geometries: + supportedFeatures.unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + // ...except IN_UNIT: + supportedFeatures.unsupport(InUnitFunction.FEATURE); + } + + @Override + public final FeatureSet getSupportedFeatures() { + return supportedFeatures; } @Override @@ -177,7 +228,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { } @Override - public String translate(final UnitConversionFunction fct) throws TranslationException { + public String translate(final InUnitFunction fct) throws TranslationException { return getDefaultADQLFunction(fct); } diff --git a/src/adql/translator/SQLServerTranslator.java b/src/adql/translator/SQLServerTranslator.java index 1f6606128339fa8116e408c62a5a0ab7ecc140be..677e671b85a9216e19411d97d130554d73f0e640 100644 --- a/src/adql/translator/SQLServerTranslator.java +++ b/src/adql/translator/SQLServerTranslator.java @@ -29,19 +29,22 @@ import adql.db.STCS.Region; import adql.db.SearchColumnList; import adql.db.exception.UnresolvedJoinException; import adql.parser.SQLServer_ADQLQueryFactory; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.ADQLQuery; import adql.query.ClauseSelect; import adql.query.IdentifierField; import adql.query.constraint.Comparison; +import adql.query.constraint.ComparisonOperator; import adql.query.from.ADQLJoin; import adql.query.from.ADQLTable; import adql.query.from.FromContent; import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; import adql.query.operand.Concatenation; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.MathFunction; -import adql.query.operand.function.UnitConversionFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -73,9 +76,16 @@ import adql.query.operand.function.geometry.RegionFunction; * TODO Check MS SQL Server datatypes (see {@link #convertTypeFromDB(int, String, String, String[])}, * {@link #convertTypeToDB(DBType)}). * - * <p><i><b>Important note:</b> - * Geometrical functions are not translated ; the translation returned for them - * is their ADQL expression. + * <p><i><b>Important note 1:</b> + * Geometrical functions and IN_UNIT are not translated ; the translation + * returned for them is their ADQL expression. + * </i></p> + * + * <p><i><b>Important note 2:</b> + * If new optional features are supported in an extension of this translator, + * they should be visible in {@link #getSupportedFeatures()}. To customize this + * list, you must overwrite {@link #initSupportedFeatures()} and update in + * there the attribute {@link #supportedFeatures}. * </i></p> * * @author Grégory Mantelet (ARI;CDS) @@ -95,37 +105,97 @@ public class SQLServerTranslator extends JDBCTranslator { */ protected byte caseSensitivity = 0x00; + /** List of all optional features supported by this translator. + * <p><i><b>Note:</b> + * This list can be customized by extending this translator and then + * overwriting {@link #initSupportedFeatures()}. + * </i></p> + * @since 2.0 */ + protected final FeatureSet supportedFeatures = new FeatureSet(); + /** - * Builds an SQLServerTranslator which always translates in SQL all identifiers (schema, table and column) in a case sensitive manner ; - * in other words, schema, table and column names will be surrounded by double quotes in the SQL translation. + * Builds an SQLServerTranslator which always translates in SQL all + * identifiers (schema, table and column) in a case sensitive manner ; in + * other words, schema, table and column names will be surrounded by double + * quotes in the SQL translation. */ public SQLServerTranslator() { caseSensitivity = 0x0F; + initSupportedFeatures(); } /** - * Builds an SQLServerTranslator which always translates in SQL all identifiers (schema, table and column) in the specified case sensitivity ; - * in other words, schema, table and column names will all be surrounded or not by double quotes in the SQL translation. + * Builds an SQLServerTranslator which always translates in SQL all + * identifiers (schema, table and column) in the specified case + * sensitivity ; in other words, schema, table and column names will all be + * surrounded or not by double quotes in the SQL translation. * - * @param allCaseSensitive <i>true</i> to translate all identifiers in a case sensitive manner (surrounded by double quotes), <i>false</i> for case insensitivity. + * @param allCaseSensitive <code>true</code> to translate all identifiers + * in a case sensitive manner (surrounded by double + * quotes), + * <code>false</code> for case insensitivity. */ public SQLServerTranslator(final boolean allCaseSensitive) { caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; + initSupportedFeatures(); } /** - * Builds an SQLServerTranslator which will always translate in SQL identifiers with the defined case sensitivity. + * Builds an SQLServerTranslator which will always translate in SQL + * identifiers with the defined case sensitivity. * - * @param catalog <i>true</i> to translate catalog names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise. - * @param schema <i>true</i> to translate schema names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise. - * @param table <i>true</i> to translate table names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise. - * @param column <i>true</i> to translate column names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise. + * @param catalog <code>true</code> to translate catalog names with double + * quotes (case sensitive in the DBMS), + * <code>false</code> otherwise. + * @param schema <code>true</code> to translate schema names with double + * quotes (case sensitive in the DBMS), + * <code>false</code> otherwise. + * @param table <code>true</code> to translate table names with double + * quotes (case sensitive in the DBMS), + * <code>false</code> otherwise. + * @param column <code>true</code> to translate column names with double + * quotes (case sensitive in the DBMS), + * <code>false</code> otherwise. */ public SQLServerTranslator(final boolean catalog, final boolean schema, final boolean table, final boolean column) { caseSensitivity = IdentifierField.CATALOG.setCaseSensitive(caseSensitivity, catalog); caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema); caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table); caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column); + initSupportedFeatures(); + } + + /** + * Initialize the list of optional features supported by this translator. + * + * <p> + * By default, all optional features are supported except the following: + * </p> + * <ul> + * <li>All geometric functions,</li> + * <li>ILIKE,</li> + * <li>and IN_UNIT</li> + * </ul> + * + * @since 2.0 + */ + protected void initSupportedFeatures() { + // Any UDF allowed: + supportedFeatures.allowAnyUdf(true); + + // Support all features... + supportedFeatures.supportAll(); + // ...except all geometries: + supportedFeatures.unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + // ...except ILIKE: + supportedFeatures.unsupport(ComparisonOperator.ILIKE.getFeatureDescription()); + // ...except IN_UNIT: + supportedFeatures.unsupport(InUnitFunction.FEATURE); + } + + @Override + public final FeatureSet getSupportedFeatures() { + return supportedFeatures; } @Override @@ -202,7 +272,7 @@ public class SQLServerTranslator extends JDBCTranslator { } @Override - public String translate(final UnitConversionFunction fct) throws TranslationException { + public String translate(final InUnitFunction fct) throws TranslationException { return getDefaultADQLFunction(fct); } diff --git a/test/adql/translator/TestJDBCTranslator.java b/test/adql/translator/TestJDBCTranslator.java index 5a24c3f38daca3c96523a39f99d549242c3933c1..dbd3cbfc2b2269ffd1977762e61945fdf1948c0c 100644 --- a/test/adql/translator/TestJDBCTranslator.java +++ b/test/adql/translator/TestJDBCTranslator.java @@ -11,6 +11,8 @@ import adql.db.FunctionDef; import adql.db.STCS.Region; import adql.parser.ADQLParser; import adql.parser.ADQLParser.ADQLVersion; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.ADQLQuery; import adql.query.IdentifierField; @@ -20,7 +22,7 @@ import adql.query.operand.NumericConstant; import adql.query.operand.Operation; import adql.query.operand.StringConstant; import adql.query.operand.function.DefaultUDF; -import adql.query.operand.function.UnitConversionFunction; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -40,6 +42,13 @@ public class TestJDBCTranslator { public void setUp() throws Exception { } + public final static int countFeatures(final FeatureSet features) { + int cnt = 0; + for(LanguageFeature feat : features) + cnt++; + return cnt; + } + @Test public void testTranslateComplexNumericOperation() { JDBCTranslator tr = new AJDBCTranslator(); @@ -175,7 +184,12 @@ public class TestJDBCTranslator { public final static class AJDBCTranslator extends JDBCTranslator { @Override - public String translate(UnitConversionFunction fct) throws TranslationException { + public FeatureSet getSupportedFeatures() { + return new FeatureSet(true, true); + } + + @Override + public String translate(InUnitFunction fct) throws TranslationException { return null; } diff --git a/test/adql/translator/TestMySQLTranslator.java b/test/adql/translator/TestMySQLTranslator.java index cb794ceadd6c558b51de823611dc0f505312bd51..afdf024995180fa24c14258a208746de8d0048c3 100644 --- a/test/adql/translator/TestMySQLTranslator.java +++ b/test/adql/translator/TestMySQLTranslator.java @@ -1,13 +1,20 @@ package adql.translator; +import static adql.translator.TestJDBCTranslator.countFeatures; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; import adql.parser.ADQLParser; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.ADQLQuery; +import adql.query.constraint.ComparisonOperator; +import adql.query.operand.function.InUnitFunction; public class TestMySQLTranslator { @@ -34,4 +41,28 @@ public class TestMySQLTranslator { } } + @Test + public void testSupportedFeatures() { + final FeatureSet supportedFeatures = (new MySQLTranslator()).getSupportedFeatures(); + + // TEST: Not NULL: + assertNotNull(supportedFeatures); + + // TEST: Any UDF should be allowed, by default: + assertTrue(supportedFeatures.isAnyUdfAllowed()); + + // Create the list of all expected supported features: + final FeatureSet expectedFeatures = new FeatureSet(true, true); + expectedFeatures.unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + expectedFeatures.unsupport(ComparisonOperator.ILIKE.getFeatureDescription()); + expectedFeatures.unsupport(InUnitFunction.FEATURE); + + // TEST: same number of features: + assertEquals(countFeatures(expectedFeatures), countFeatures(supportedFeatures)); + + // TEST: same features: + for(LanguageFeature expected : expectedFeatures) + assertTrue(supportedFeatures.isSupporting(expected)); + } + } diff --git a/test/adql/translator/TestPgSphereTranslator.java b/test/adql/translator/TestPgSphereTranslator.java index 0f48b09d0b94986c2805da3f73e9b407ec1d5f4d..29d22fb8f699d5c340675f702edf50907f063491 100644 --- a/test/adql/translator/TestPgSphereTranslator.java +++ b/test/adql/translator/TestPgSphereTranslator.java @@ -1,5 +1,6 @@ package adql.translator; +import static adql.translator.TestJDBCTranslator.countFeatures; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -20,9 +21,12 @@ import adql.db.DBType; import adql.db.DBType.DBDatatype; import adql.db.STCS.Region; import adql.parser.ADQLParser; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.operand.NumericConstant; import adql.query.operand.StringConstant; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.geometry.CentroidFunction; import adql.query.operand.function.geometry.CircleFunction; import adql.query.operand.function.geometry.GeometryFunction; @@ -387,4 +391,26 @@ public class TestPgSphereTranslator { } } + @Test + public void testSupportedFeatures() { + final FeatureSet supportedFeatures = (new PgSphereTranslator()).getSupportedFeatures(); + + // TEST: Not NULL: + assertNotNull(supportedFeatures); + + // TEST: Any UDF should be allowed, by default: + assertTrue(supportedFeatures.isAnyUdfAllowed()); + + // Create the list of all expected supported features: + final FeatureSet expectedFeatures = new FeatureSet(true, true); + expectedFeatures.unsupport(InUnitFunction.FEATURE); + + // TEST: same number of features: + assertEquals(countFeatures(expectedFeatures), countFeatures(supportedFeatures)); + + // TEST: same features: + for(LanguageFeature expected : expectedFeatures) + assertTrue(supportedFeatures.isSupporting(expected)); + } + } diff --git a/test/adql/translator/TestPostgreSQLTranslator.java b/test/adql/translator/TestPostgreSQLTranslator.java index 01fef8e2fea34d1ab87e8f9224ba78e56f8e5e67..00a365d72440868e02fbb1fce362e71025134af2 100644 --- a/test/adql/translator/TestPostgreSQLTranslator.java +++ b/test/adql/translator/TestPostgreSQLTranslator.java @@ -1,29 +1,36 @@ package adql.translator; +import static adql.translator.TestJDBCTranslator.countFeatures; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.query.operand.NumericConstant; +import adql.query.operand.function.InUnitFunction; import adql.query.operand.function.MathFunction; import adql.query.operand.function.MathFunctionType; public class TestPostgreSQLTranslator { @Before - public void setUp() throws Exception{} + public void setUp() throws Exception { + } @Test - public void testTranslateMathFunction(){ + public void testTranslateMathFunction() { // Check that all math functions, except PI, operates a cast to their DOUBLE/REAL parameters: PostgreSQLTranslator trans = new PostgreSQLTranslator(); MathFunctionType[] types = MathFunctionType.values(); NumericConstant num = new NumericConstant("1.234"), prec = new NumericConstant("2"); - for(MathFunctionType type : types){ - try{ - switch(type){ + for(MathFunctionType type : types) { + try { + switch(type) { case PI: assertEquals("PI()", trans.translate(new MathFunction(type))); break; @@ -52,11 +59,34 @@ public class TestPostgreSQLTranslator { assertEquals(type + "(CAST(1.234 AS numeric), CAST(1.234 AS numeric))", trans.translate(new MathFunction(type, num, num))); break; } - }catch(Exception ex){ + } catch(Exception ex) { ex.printStackTrace(); fail("Translation exception for the type \"" + type + "\": " + ex.getMessage()); } } } + @Test + public void testSupportedFeatures() { + final FeatureSet supportedFeatures = (new PostgreSQLTranslator()).getSupportedFeatures(); + + // TEST: Not NULL: + assertNotNull(supportedFeatures); + + // TEST: Any UDF should be allowed, by default: + assertTrue(supportedFeatures.isAnyUdfAllowed()); + + // Create the list of all expected supported features: + final FeatureSet expectedFeatures = new FeatureSet(true, true); + expectedFeatures.unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + expectedFeatures.unsupport(InUnitFunction.FEATURE); + + // TEST: same number of features: + assertEquals(countFeatures(expectedFeatures), countFeatures(supportedFeatures)); + + // TEST: same features: + for(LanguageFeature expected : expectedFeatures) + assertTrue(supportedFeatures.isSupporting(expected)); + } + } diff --git a/test/adql/translator/TestSQLServerTranslator.java b/test/adql/translator/TestSQLServerTranslator.java index f8eda775f0bb598364a584d7052885bfa616d11a..1629efb0df5dbee4fda337fefc543defd849f4da 100644 --- a/test/adql/translator/TestSQLServerTranslator.java +++ b/test/adql/translator/TestSQLServerTranslator.java @@ -1,6 +1,9 @@ package adql.translator; +import static adql.translator.TestJDBCTranslator.countFeatures; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; @@ -16,8 +19,12 @@ import adql.db.DefaultDBTable; import adql.parser.ADQLParser; import adql.parser.ADQLParser.ADQLVersion; import adql.parser.SQLServer_ADQLQueryFactory; +import adql.parser.feature.FeatureSet; +import adql.parser.feature.LanguageFeature; import adql.parser.grammar.ParseException; import adql.query.ADQLQuery; +import adql.query.constraint.ComparisonOperator; +import adql.query.operand.function.InUnitFunction; public class TestSQLServerTranslator { @@ -143,4 +150,28 @@ public class TestSQLServerTranslator { } } + @Test + public void testSupportedFeatures() { + final FeatureSet supportedFeatures = (new SQLServerTranslator()).getSupportedFeatures(); + + // TEST: Not NULL: + assertNotNull(supportedFeatures); + + // TEST: Any UDF should be allowed, by default: + assertTrue(supportedFeatures.isAnyUdfAllowed()); + + // Create the list of all expected supported features: + final FeatureSet expectedFeatures = new FeatureSet(true, true); + expectedFeatures.unsupportAll(LanguageFeature.TYPE_ADQL_GEO); + expectedFeatures.unsupport(ComparisonOperator.ILIKE.getFeatureDescription()); + expectedFeatures.unsupport(InUnitFunction.FEATURE); + + // TEST: same number of features: + assertEquals(countFeatures(expectedFeatures), countFeatures(supportedFeatures)); + + // TEST: same features: + for(LanguageFeature expected : expectedFeatures) + assertTrue(supportedFeatures.isSupporting(expected)); + } + }