diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java index 4477e6f91d180e8bccc3c607b832d7bb12d8fec6..b24f5adb2dcaa30666d85c23598f0866ca2996b7 100644 --- a/src/adql/parser/ADQLQueryFactory.java +++ b/src/adql/parser/ADQLQueryFactory.java @@ -51,6 +51,7 @@ import adql.query.from.OuterJoin; import adql.query.from.OuterJoin.OuterType; import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; +import adql.query.operand.BitNotOperand; import adql.query.operand.Concatenation; import adql.query.operand.NegativeOperand; import adql.query.operand.NumericConstant; @@ -248,6 +249,21 @@ public class ADQLQueryFactory { return new NegativeOperand(opToNegativate); } + /** + * Create the object representation of the bitwise operation NOT (i.e. + * binary complement) applied to the given operand. + * + * @param operandToBitNot Operand whose binary complement must be + * computed. + * + * @return The corresponding object representation of this bitwise + * operation. + * + * @since 2.0 */ + public BitNotOperand createBitNotOperand(ADQLOperand operandToBitNot) throws Exception { + return new BitNotOperand(operandToBitNot); + } + public Concatenation createConcatenation() throws Exception { return new Concatenation(); } diff --git a/src/adql/parser/feature/FeatureSet.java b/src/adql/parser/feature/FeatureSet.java index ba36845286d460f91c316b3450791bd774313ec8..69ea872fb25095a4a02f1f1335e4939b22de4183 100644 --- a/src/adql/parser/feature/FeatureSet.java +++ b/src/adql/parser/feature/FeatureSet.java @@ -30,6 +30,8 @@ import java.util.Set; import adql.db.FunctionDef; import adql.query.ClauseOffset; import adql.query.constraint.ComparisonOperator; +import adql.query.operand.BitNotOperand; +import adql.query.operand.OperationType; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -147,7 +149,7 @@ import adql.query.operand.function.string.LowerFunction; * </ul> * * @author Grégory Mantelet (CDS) - * @version 2.0 (07/2019) + * @version 2.0 (08/2019) * @since 2.0 */ public class FeatureSet implements Iterable<LanguageFeature> { @@ -584,14 +586,7 @@ public class FeatureSet implements Iterable<LanguageFeature> { public static final LanguageFeature CAST = new LanguageFeature(FeatureType.ADQL_TYPE, "CAST"); // TODO CAST - public static final LanguageFeature IN_UNIT = new LanguageFeature(FeatureType.ADQL_UNIT, "IN_UNIT"); // TODO IN_UNIT - - public static final LanguageFeature BIT_AND = new LanguageFeature(FeatureType.ADQL_BITWISE, "BIT_AND"); // TODO BIT_AND - public static final LanguageFeature BIT_OR = new LanguageFeature(FeatureType.ADQL_BITWISE, "BIT_OR"); // TODO BIT_OR - public static final LanguageFeature BIT_XOR = new LanguageFeature(FeatureType.ADQL_BITWISE, "BIT_XOR"); // TODO BIT_XOR - public static final LanguageFeature BIT_NOT = new LanguageFeature(FeatureType.ADQL_BITWISE, "BIT_NOT"); // TODO BIT_NOT - - public static final LanguageFeature OFFSET = new LanguageFeature(FeatureType.ADQL_OFFSET, "OFFSET"); // TODO OFFSET*/ + public static final LanguageFeature IN_UNIT = new LanguageFeature(FeatureType.ADQL_UNIT, "IN_UNIT"); // TODO IN_UNIT*/ /** All standard features available. * <p> @@ -603,7 +598,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[]{ 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[]{ 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/grammar/ADQLGrammarBase.java b/src/adql/parser/grammar/ADQLGrammarBase.java index fafa1d32911c529ff8a9ebdaf9f7a57014698dab..eecb0cb18154f02da96863f76f39e7ee830e93e9 100644 --- a/src/adql/parser/grammar/ADQLGrammarBase.java +++ b/src/adql/parser/grammar/ADQLGrammarBase.java @@ -110,9 +110,9 @@ public abstract class ADQLGrammarBase implements ADQLGrammar { else if (!isRegularIdentifier(token.image)) { String message = "Invalid ADQL regular identifier: \u005c"" + token.image + "\u005c"!"; if (getVersion() == ADQLVersion.V2_0 && token.image.matches("0[Xx][0-9a-fA-F]+")) - message += " HINT: hexadecimal values are not supported in ADQL-2.0. You should change the grammar version of the ADQL parser to at least ADQL-2.1."; + message += System.getProperty("line.separator", "\n") + "(HINT: hexadecimal values are not supported in ADQL-2.0. You should upgrade your ADQL parser to support at least ADQL-2.1.)"; else - message += " HINT: If it aims to be a column/table name/alias, you should write it between double quotes."; + message += System.getProperty("line.separator", "\n") + " (HINT: If it aims to be a column/table name/alias, you should write it between double quotes.)"; throw new ParseException(message, new TextPosition(token)); } } diff --git a/src/adql/parser/grammar/ParseException.java b/src/adql/parser/grammar/ParseException.java index 87a63884d336f38f323c4bfee6517140052f55a3..8429516613375e2c4c0ca801586feaef3c027160 100644 --- a/src/adql/parser/grammar/ParseException.java +++ b/src/adql/parser/grammar/ParseException.java @@ -28,6 +28,8 @@ * Modified by Grégory Mantelet (CDS), on Aug. 2019 * Modifications: * - change the package name + * - customize error message in function of the token: append an hint + * message or a possible cause in case of TokenMgrError * * /!\ DO NOT RE-GENERATE THIS FILE /!\ * In case of re-generation, replace it by ParseException.java.backup (but maybe @@ -36,6 +38,7 @@ */ package adql.parser.grammar; +import adql.parser.ADQLParser.ADQLVersion; import adql.query.TextPosition; /** @@ -46,6 +49,10 @@ import adql.query.TextPosition; * * You can modify this class to customize your error reporting * mechanisms so long as you retain the public fields. + * + * @version 2.0 (08/2019) + * @author JavaCC + * @author Grégory Mantelet (CDS) */ public class ParseException extends Exception { @@ -101,9 +108,9 @@ public class ParseException extends Exception { private final static String buildExpandedMessage(final TokenMgrError err) { if (err.getMessage().indexOf("<EOF>") > 0) - return err.getMessage() + "! Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!)."; + return err.getMessage() + "!" + eol + "Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!)."; else - return err.getMessage() + "! Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it)."; + return err.getMessage() + "!" + eol + "Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it)."; } /** @@ -190,9 +197,11 @@ public class ParseException extends Exception { if (maxSize == 1) { tok = currentToken.next; if (tok.adqlReserved) - msg.append(System.getProperty("line.separator", "\n")).append("(HINT: \"").append(tok.image).append("\" is a reserved ADQL word in " + currentToken.next.adqlVersion + ". To use it as a column/table/schema name/alias, write it between double quotes.)"); + msg.append(eol).append("(HINT: \"").append(tok.image).append("\" is a reserved ADQL word in " + currentToken.next.adqlVersion + ". To use it as a column/table/schema name/alias, write it between double quotes.)"); else if (tok.sqlReserved) - msg.append(System.getProperty("line.separator", "\n")).append("(HINT: \"").append(tok.image).append("\" is not supported in ADQL " + currentToken.next.adqlVersion + ", but is however a reserved word. To use it as a column/table/schema name/alias, write it between double quotes.)"); + msg.append(eol).append("(HINT: \"").append(tok.image).append("\" is not supported in ADQL " + currentToken.next.adqlVersion + ", but is however a reserved word. To use it as a column/table/schema name/alias, write it between double quotes.)"); + else if (tok.adqlVersion == ADQLVersion.V2_0 && tok.image.matches("[~^|&]")) + msg.append(eol).append("(HINT: \"").append(tok.image).append("\" bitwise operations are not supported in ADQL-2.0. You should migrate your ADQL parser to support at least ADQL-2.1.)"); } return msg.toString(); @@ -239,7 +248,7 @@ public class ParseException extends Exception { /** * The end of line string for this machine. */ - protected String eol = System.getProperty("line.separator", "\n"); + protected static String eol = System.getProperty("line.separator", "\n"); /** * Used to convert raw characters to their escaped version diff --git a/src/adql/parser/grammar/ParseException.java.backup b/src/adql/parser/grammar/ParseException.java.backup index 87a63884d336f38f323c4bfee6517140052f55a3..8429516613375e2c4c0ca801586feaef3c027160 100644 --- a/src/adql/parser/grammar/ParseException.java.backup +++ b/src/adql/parser/grammar/ParseException.java.backup @@ -28,6 +28,8 @@ * Modified by Grégory Mantelet (CDS), on Aug. 2019 * Modifications: * - change the package name + * - customize error message in function of the token: append an hint + * message or a possible cause in case of TokenMgrError * * /!\ DO NOT RE-GENERATE THIS FILE /!\ * In case of re-generation, replace it by ParseException.java.backup (but maybe @@ -36,6 +38,7 @@ */ package adql.parser.grammar; +import adql.parser.ADQLParser.ADQLVersion; import adql.query.TextPosition; /** @@ -46,6 +49,10 @@ import adql.query.TextPosition; * * You can modify this class to customize your error reporting * mechanisms so long as you retain the public fields. + * + * @version 2.0 (08/2019) + * @author JavaCC + * @author Grégory Mantelet (CDS) */ public class ParseException extends Exception { @@ -101,9 +108,9 @@ public class ParseException extends Exception { private final static String buildExpandedMessage(final TokenMgrError err) { if (err.getMessage().indexOf("<EOF>") > 0) - return err.getMessage() + "! Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!)."; + return err.getMessage() + "!" + eol + "Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!)."; else - return err.getMessage() + "! Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it)."; + return err.getMessage() + "!" + eol + "Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it)."; } /** @@ -190,9 +197,11 @@ public class ParseException extends Exception { if (maxSize == 1) { tok = currentToken.next; if (tok.adqlReserved) - msg.append(System.getProperty("line.separator", "\n")).append("(HINT: \"").append(tok.image).append("\" is a reserved ADQL word in " + currentToken.next.adqlVersion + ". To use it as a column/table/schema name/alias, write it between double quotes.)"); + msg.append(eol).append("(HINT: \"").append(tok.image).append("\" is a reserved ADQL word in " + currentToken.next.adqlVersion + ". To use it as a column/table/schema name/alias, write it between double quotes.)"); else if (tok.sqlReserved) - msg.append(System.getProperty("line.separator", "\n")).append("(HINT: \"").append(tok.image).append("\" is not supported in ADQL " + currentToken.next.adqlVersion + ", but is however a reserved word. To use it as a column/table/schema name/alias, write it between double quotes.)"); + msg.append(eol).append("(HINT: \"").append(tok.image).append("\" is not supported in ADQL " + currentToken.next.adqlVersion + ", but is however a reserved word. To use it as a column/table/schema name/alias, write it between double quotes.)"); + else if (tok.adqlVersion == ADQLVersion.V2_0 && tok.image.matches("[~^|&]")) + msg.append(eol).append("(HINT: \"").append(tok.image).append("\" bitwise operations are not supported in ADQL-2.0. You should migrate your ADQL parser to support at least ADQL-2.1.)"); } return msg.toString(); @@ -239,7 +248,7 @@ public class ParseException extends Exception { /** * The end of line string for this machine. */ - protected String eol = System.getProperty("line.separator", "\n"); + protected static String eol = System.getProperty("line.separator", "\n"); /** * Used to convert raw characters to their escaped version diff --git a/src/adql/parser/grammar/adqlGrammar200.jj b/src/adql/parser/grammar/adqlGrammar200.jj index 3a16e9b38d96d7a2cb3185743c0a08bc76e81025..c622b8a35901499ed45ef3b1297e71b075cf1733 100644 --- a/src/adql/parser/grammar/adqlGrammar200.jj +++ b/src/adql/parser/grammar/adqlGrammar200.jj @@ -229,6 +229,16 @@ TOKEN : { | < DIVIDE: "/" > } +/* **************** */ +/* Binary operators */ +/* **************** */ +TOKEN : { + < BIT_AND: "&" > +| < BIT_OR: "|" > +| < BIT_XOR: "^" > +| < BIT_NOT: "~" > +} + /* ******************** */ /* Comparison operators */ /* ******************** */ @@ -396,7 +406,7 @@ TOKEN : { TOKEN : { < REGULAR_IDENTIFIER_CANDIDATE: ((<Letter>)+ (<DIGIT> | <Letter>)* | (<DIGIT>)+ <Letter> (<DIGIT> | <Letter>)*) > -| < #Letter: ["a"-"z","A"-"Z","_","?","$","@","^","#","`","~","[","]","{","}"] > +| < #Letter: ["a"-"z","A"-"Z","_","?","$","@","#","`","[","]","{","}"] > } /* ########## */ diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj index dfce6096558b470d74c8abdfb30c095b30f08505..2a5443ba611d7551979fbde2b5248cb069705338 100644 --- a/src/adql/parser/grammar/adqlGrammar201.jj +++ b/src/adql/parser/grammar/adqlGrammar201.jj @@ -232,6 +232,16 @@ TOKEN : { | < DIVIDE: "/" > } +/* **************** */ +/* Binary operators */ +/* **************** */ +TOKEN : { + < BIT_AND: "&" > +| < BIT_OR: "|" > +| < BIT_XOR: "^" > +| < BIT_NOT: "~" > +} + /* ******************** */ /* Comparison operators */ /* ******************** */ @@ -409,7 +419,7 @@ TOKEN : { TOKEN : { < REGULAR_IDENTIFIER_CANDIDATE: ((<Letter>)+ (<DIGIT> | <Letter>)* | (<DIGIT>)+ <Letter> (<DIGIT> | <Letter>)*) > -| < #Letter: ["a"-"z","A"-"Z","_","?","!","$","@","^","#","`","~","[","]","{","}"] > +| < #Letter: ["a"-"z","A"-"Z","_","?","!","$","@","#","`","[","]","{","}"] > } /* ########## */ @@ -865,7 +875,7 @@ ADQLOperand StringValueExpressionPrimary(): {StringConstant expr; ADQLColumn col ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; } { try{ - (LOOKAHEAD((<PLUS>|<MINUS>) | (Factor() (<PLUS>|<MINUS>|<ASTERISK>|<DIVIDE>))) valueExpr=NumericExpression() + (LOOKAHEAD((<PLUS>|<MINUS>|<BIT_NOT>) | (Factor() (<PLUS>|<MINUS>|<ASTERISK>|<DIVIDE>|<BIT_AND>|<BIT_OR>|<BIT_XOR>))) valueExpr=NumericExpression() | LOOKAHEAD(<COORDSYS> | <LOWER> | (StringFactor() <CONCAT>)) valueExpr=StringExpression() | LOOKAHEAD(<LEFT_PAR>) left=<LEFT_PAR> valueExpr=ValueExpression() right=<RIGHT_PAR> { valueExpr = queryFactory.createWrappedOperand(valueExpr); ((WrappedOperand)valueExpr).setPosition(new TextPosition(left, right)); } | LOOKAHEAD(<REGULAR_IDENTIFIER_CANDIDATE> <LEFT_PAR>) valueExpr=UserDefinedFunction() @@ -890,8 +900,25 @@ ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; } } -ADQLOperand NumericExpression(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} { - (leftOp=NumericTerm() ((sign=<PLUS> | sign=<MINUS>) rightOp=NumericExpression())?) +ADQLOperand NumericExpression(): { Token operator = null; ADQLOperand leftOp, rightOp = null; } { + (leftOp=NumericTerm() ((operator=<BIT_AND> | operator=<BIT_OR> | operator=<BIT_XOR>) rightOp=NumericExpression())?) + { + if (operator == null) + return leftOp; + else{ + try{ + Operation operation = queryFactory.createOperation(leftOp, OperationType.getOperator(operator.image), rightOp); + operation.setPosition(new TextPosition(leftOp.getPosition(), rightOp.getPosition())); + return operation; + }catch(Exception ex){ + throw generateParseException(ex); + } + } + } +} + +ADQLOperand NumericTerm(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} { + (leftOp=Term() ((sign=<PLUS> | sign=<MINUS>) rightOp=NumericTerm())?) { if (sign == null) return leftOp; @@ -907,8 +934,8 @@ ADQLOperand NumericExpression(): {Token sign=null; ADQLOperand leftOp, rightOp=n } } -ADQLOperand NumericTerm(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} { - (leftOp=Factor() ((sign=<ASTERISK> | sign=<DIVIDE>) rightOp=NumericTerm())?) +ADQLOperand Term(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} { + (leftOp=Factor() ((sign=<ASTERISK> | sign=<DIVIDE>) rightOp=Term())?) { if (sign == null) return leftOp; @@ -924,28 +951,26 @@ ADQLOperand NumericTerm(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} } } -ADQLOperand Factor(): {boolean negative = false; Token minusSign = null; ADQLOperand op;} { +ADQLOperand Factor(): {Token minusSign = null, bitNot = null; ADQLOperand op;} { ( - (<PLUS> | (minusSign=<MINUS> {negative = true;}))? + (<PLUS> | minusSign=<MINUS> | bitNot=<BIT_NOT>)? (LOOKAHEAD(2) op=NumericFunction() | op=NumericValueExpressionPrimary()) ) - { - if (negative){ - try{ - TextPosition position = op.getPosition(); - op = queryFactory.createNegativeOperand(op); - NegativeOperand negativeOp = (NegativeOperand)op; - if (minusSign != null) - negativeOp.setPosition(new TextPosition(minusSign.beginLine, minusSign.beginColumn, position.endLine, position.endColumn)); - else - negativeOp.setPosition(position); - }catch(Exception ex){ - throw generateParseException(ex); - } + try { + if (bitNot != null) { + BitNotOperand bitNotOperand = queryFactory.createBitNotOperand(op); + bitNotOperand.setPosition(new TextPosition(new TextPosition(bitNot), op.getPosition())); + return bitNotOperand; + } else if (minusSign != null) { + NegativeOperand negativeOp = queryFactory.createNegativeOperand(op); + negativeOp.setPosition(new TextPosition(new TextPosition(minusSign), op.getPosition())); + return negativeOp; + } else + return op; + }catch(Exception ex){ + throw generateParseException(ex); } - - return op; } } diff --git a/src/adql/query/operand/BitNotOperand.java b/src/adql/query/operand/BitNotOperand.java new file mode 100644 index 0000000000000000000000000000000000000000..cf9c78da65a793ad39bc80f7e2f2df70833dcc79 --- /dev/null +++ b/src/adql/query/operand/BitNotOperand.java @@ -0,0 +1,187 @@ +package adql.query.operand; + +/* + * This file is part of ADQLLibrary. + * + * ADQLLibrary is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ADQLLibrary is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * 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 2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + */ + +import java.util.NoSuchElementException; + +import adql.parser.feature.LanguageFeature; +import adql.query.ADQLIterator; +import adql.query.ADQLObject; +import adql.query.TextPosition; + +/** + * Operand that represents the binary complement (bitwise NOT) of a given + * numeric operand. + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (08/2019) + * @since 2.0 + */ +public final class BitNotOperand implements ADQLOperand { + + /** Description of this ADQL Feature. */ + public static final LanguageFeature FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_BITWISE, "BIT_NOT", true); + + /** The operand whose bits must be reversed. */ + private ADQLOperand operand; + + /** Position of this operand. */ + private TextPosition position = null; + + /** + * Builds an operand which computes the binary complement of the given + * operand. + * + * <p><i><b>Important:</b> + * The given operand must be numeric ({@link ADQLOperand#isNumeric()} must + * return <code>true</code>)! + * </b></p> + * + * @param operand The operand whose binary complement must be computed. + * + * @throws NullPointerException If the given operand is NULL. + * @throws UnsupportedOperationException If the given operand is not + * numeric (if {@link ADQLOperand#isNumeric()} + * does not return + * <code>true</code>). + */ + public BitNotOperand(ADQLOperand operand) throws NullPointerException, UnsupportedOperationException { + if (operand == null) + throw new NullPointerException("Impossible to apply a bitwise NOT on NULL!"); + + if (operand.isNumeric()) + this.operand = operand; + else + throw new UnsupportedOperationException("Impossible to apply a bitwise NOT on a non-numeric operand (" + operand.toADQL() + ")!"); + } + + @Override + public final LanguageFeature getFeatureDescription() { + return FEATURE; + } + + /** + * Gets the operand whose binary complement must be computed. + * + * @return The operand to binary-complement. + */ + public final ADQLOperand getOperand() { + return operand; + } + + /** Always returns <code>true</code>. + * @see adql.query.operand.ADQLOperand#isNumeric() + */ + @Override + public final boolean isNumeric() { + return true; + } + + /** Always returns <code>false</code>. + * @see adql.query.operand.ADQLOperand#isString() + */ + @Override + public final boolean isString() { + return false; + } + + @Override + public final TextPosition getPosition() { + return this.position; + } + + /** + * Sets the position at which this {@link BitNotOperand} has been found in + * the original ADQL query string. + * + * @param position Position of this {@link BitNotOperand}. + * @since 1.4 + */ + public final void setPosition(final TextPosition position) { + this.position = position; + } + + /** Always returns <code>false</code>. + * @see adql.query.operand.ADQLOperand#isGeometry() + */ + @Override + public final boolean isGeometry() { + return false; + } + + @Override + public ADQLObject getCopy() throws Exception { + BitNotOperand copy = new BitNotOperand((ADQLOperand)operand.getCopy()); + return copy; + } + + @Override + public String getName() { + return "BIT_NOT_" + operand.getName(); + } + + @Override + public ADQLIterator adqlIterator() { + return new ADQLIterator() { + + private boolean operandGot = (operand == null); + + @Override + public ADQLObject next() { + if (operandGot) + throw new NoSuchElementException(); + operandGot = true; + return operand; + } + + @Override + public boolean hasNext() { + return !operandGot; + } + + @Override + public void replace(ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException { + if (!operandGot) + throw new IllegalStateException("replace(ADQLObject) impossible: next() has not yet been called!"); + + if (replacer == null) + remove(); + else if (replacer instanceof ADQLOperand && ((ADQLOperand)replacer).isNumeric()) + operand = (ADQLOperand)replacer; + else + throw new UnsupportedOperationException("Impossible to replace the operand \"" + operand.toADQL() + "\" by \"" + replacer.toADQL() + "\" in the BitNotOperand \"" + toADQL() + "\" because the replacer is not an ADQLOperand or is not numeric!"); + } + + @Override + public void remove() { + if (!operandGot) + throw new IllegalStateException("remove() impossible: next() has not yet been called!"); + else + throw new UnsupportedOperationException("Impossible to remove the only operand (" + operand.toADQL() + ") of a BitNotOperand (" + toADQL() + "). However you can remove the whole BitNotOperand."); + } + }; + } + + @Override + public String toADQL() { + return "~" + operand.toADQL(); + } + +} diff --git a/src/adql/query/operand/Operation.java b/src/adql/query/operand/Operation.java index 0d553cbd2cd4e945de96cb58584c60f4f03759ec..9c384efb0ac2aa129868a0a72a744fb3c52a31d0 100644 --- a/src/adql/query/operand/Operation.java +++ b/src/adql/query/operand/Operation.java @@ -29,10 +29,10 @@ import adql.query.TextPosition; /** * It represents a simple numeric operation (sum, difference, multiplication and - * division). + * division) or a bitwise operation (binary AND, OR or XOR). * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (07/2019) + * @version 2.0 (08/2019) * * @see OperationType */ diff --git a/src/adql/query/operand/OperationType.java b/src/adql/query/operand/OperationType.java index 8db0705fa689ebbb6fad4f29e145e9af49d8d39c..1543a69aceb1692c3727b55fd2dc5a961bb10e5f 100644 --- a/src/adql/query/operand/OperationType.java +++ b/src/adql/query/operand/OperationType.java @@ -1,8 +1,5 @@ package adql.query.operand; -import adql.parser.feature.LanguageFeature; -import adql.query.operand.function.SQLFunction; - /* * This file is part of ADQLLibrary. * @@ -22,16 +19,28 @@ import adql.query.operand.function.SQLFunction; * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS) */ +import adql.parser.feature.LanguageFeature; +import adql.query.operand.function.SQLFunction; + /** * Type of possible simple numeric operations. * * @author Grégory Mantelet (CDS) - * @version 2.0 (07/2019) + * @version 2.0 (08/2019) * * @see Operation */ public enum OperationType { - SUM, SUB, MULT, DIV; + SUM, + SUB, + MULT, + DIV, + /** @since 2.0 */ + BIT_AND, + /** @since 2.0 */ + BIT_OR, + /** @since 2.0 */ + BIT_XOR; /** Description of the ADQL Feature based on this type. * @since 2.0 */ @@ -39,7 +48,10 @@ public enum OperationType { /** @since 2.0 */ private OperationType() { - FEATURE = new LanguageFeature(null, this.name(), false); + if (this.name().startsWith("BIT_")) + FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_BITWISE, this.name(), true); + else + FEATURE = new LanguageFeature(null, this.name(), false); } /** @@ -61,7 +73,7 @@ public enum OperationType { } public static String[] getOperators() { - return new String[]{ SUM.toString(), SUB.toString(), MULT.toString(), DIV.toString() }; + return new String[]{ SUM.toString(), SUB.toString(), MULT.toString(), DIV.toString(), BIT_AND.toString(), BIT_OR.toString(), BIT_XOR.toString() }; } public static OperationType getOperator(String str) throws UnsupportedOperationException { @@ -73,6 +85,12 @@ public enum OperationType { return MULT; else if (str.equalsIgnoreCase("/")) return DIV; + else if (str.equalsIgnoreCase("&")) + return BIT_AND; + else if (str.equalsIgnoreCase("|")) + return BIT_OR; + else if (str.equalsIgnoreCase("^")) + return BIT_XOR; else throw new UnsupportedOperationException("Numeric operation unknown: \"" + str + "\" !"); } @@ -92,6 +110,12 @@ public enum OperationType { return "*"; case DIV: return "/"; + case BIT_AND: + return "&"; + case BIT_OR: + return "|"; + case BIT_XOR: + return "^"; default: return "???"; } diff --git a/src/adql/translator/ADQLTranslator.java b/src/adql/translator/ADQLTranslator.java index 66d22c6f641cde3d6d397d87e1ec07f6a8b369c7..d52c9999efd485cd6602e0493e3f8157c151fec1 100644 --- a/src/adql/translator/ADQLTranslator.java +++ b/src/adql/translator/ADQLTranslator.java @@ -41,6 +41,7 @@ import adql.query.from.ADQLTable; import adql.query.from.FromContent; import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; +import adql.query.operand.BitNotOperand; import adql.query.operand.Concatenation; import adql.query.operand.NegativeOperand; import adql.query.operand.NumericConstant; @@ -112,6 +113,9 @@ public interface ADQLTranslator { public String translate(NegativeOperand negOp) throws TranslationException; + /** @since 2.0 */ + public String translate(BitNotOperand bitNotOp) throws TranslationException; + public String translate(NumericConstant numConst) throws TranslationException; public String translate(StringConstant strConst) throws TranslationException; diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java index 4ee0a6aa2563c0739a1446a34f413d69b8efb4f9..71b07f5914627beb2770da0822ad7bfae7839512 100644 --- a/src/adql/translator/JDBCTranslator.java +++ b/src/adql/translator/JDBCTranslator.java @@ -52,6 +52,7 @@ import adql.query.from.ADQLTable; import adql.query.from.FromContent; import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; +import adql.query.operand.BitNotOperand; import adql.query.operand.Concatenation; import adql.query.operand.NegativeOperand; import adql.query.operand.NumericConstant; @@ -673,6 +674,8 @@ public abstract class JDBCTranslator implements ADQLTranslator { return translate((Concatenation)op); else if (op instanceof NegativeOperand) return translate((NegativeOperand)op); + else if (op instanceof BitNotOperand) + return translate((BitNotOperand)op); else if (op instanceof NumericConstant) return translate((NumericConstant)op); else if (op instanceof StringConstant) @@ -727,11 +730,24 @@ public abstract class JDBCTranslator implements ADQLTranslator { return "-" + translate(negOp.getOperand()); } + @Override + public String translate(BitNotOperand bitNotOp) throws TranslationException { + return "(~" + translate(bitNotOp.getOperand()) + ")"; + } + @Override public String translate(NumericConstant numConst) throws TranslationException { if (numConst.isHexadecimal()) { try { - return "" + Long.parseLong(numConst.getValue().substring(2), 16); + + int nbDigits = numConst.getValue().length() - 2; + /*if (nbDigits <= 4) // SMALLINT + return "" + ((short)Integer.parseUnsignedInt(numConst.getValue().substring(2), 16)); + else*/ + if (nbDigits <= 8) // INTEGER + return "" + Integer.parseUnsignedInt(numConst.getValue().substring(2), 16); + else // BIGINT + return "" + Long.parseUnsignedLong(numConst.getValue().substring(2), 16); } catch(NumberFormatException nfe) { throw new TranslationException("Impossible to evaluate the given hexadecimal expression: \"" + numConst.getValue() + "\"!", nfe); } @@ -751,7 +767,7 @@ public abstract class JDBCTranslator implements ADQLTranslator { @Override public String translate(Operation op) throws TranslationException { - return translate(op.getLeftOperand()) + op.getOperation().toADQL() + translate(op.getRightOperand()); + return "(" + translate(op.getLeftOperand()) + op.getOperation().toADQL() + translate(op.getRightOperand()) + ")"; } /* ************************ */ diff --git a/src/adql/translator/MySQLTranslator.java b/src/adql/translator/MySQLTranslator.java index 28ae9229f4207c041291d16dae6cf689ad7120a9..d9d1664e42be8edbde91eb1836e9e99caa4966e3 100644 --- a/src/adql/translator/MySQLTranslator.java +++ b/src/adql/translator/MySQLTranslator.java @@ -28,6 +28,7 @@ import adql.query.IdentifierField; import adql.query.constraint.Comparison; import adql.query.operand.ADQLOperand; import adql.query.operand.Concatenation; +import adql.query.operand.Operation; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -137,6 +138,18 @@ public class MySQLTranslator extends JDBCTranslator { /* * * */ /* ********************************************************************** */ + @Override + public String translate(Operation op) throws TranslationException { + switch(op.getOperation()) { + case BIT_AND: + case BIT_OR: + case BIT_XOR: + return "CAST(" + translate(op.getLeftOperand()) + op.getOperation().toADQL() + translate(op.getRightOperand()) + " AS SIGNED)"; + default: + return super.translate(op); + } + } + @Override public String translate(Comparison comp) throws TranslationException { switch(comp.getOperator()) { diff --git a/src/adql/translator/PostgreSQLTranslator.java b/src/adql/translator/PostgreSQLTranslator.java index da27125bd98061f73efe61a4314270f2fd1f2c17..f762e5e7d143a482cd938a37bd1b194b842aefba 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-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -25,6 +25,8 @@ import adql.db.DBType.DBDatatype; import adql.db.STCS.Region; 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.MathFunction; @@ -64,7 +66,7 @@ import adql.query.operand.function.geometry.RegionFunction; * </i></p> * * @author Grégory Mantelet (CDS;ARI) - * @version 1.4 (08/2016) + * @version 2.0 (08/2019) * * @see PgSphereTranslator */ @@ -83,7 +85,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { * 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(){ + public PostgreSQLTranslator() { caseSensitivity = 0x0F; } @@ -93,7 +95,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { * * @param allCaseSensitive <i>true</i> to translate all identifiers in a case sensitive manner (surrounded by double quotes), <i>false</i> for case insensitivity. */ - public PostgreSQLTranslator(final boolean allCaseSensitive){ + public PostgreSQLTranslator(final boolean allCaseSensitive) { caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00; } @@ -105,7 +107,7 @@ public class PostgreSQLTranslator extends JDBCTranslator { * @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. */ - public PostgreSQLTranslator(final boolean catalog, final boolean schema, final boolean table, final boolean column){ + public PostgreSQLTranslator(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); @@ -113,12 +115,12 @@ public class PostgreSQLTranslator extends JDBCTranslator { } @Override - public boolean isCaseSensitive(final IdentifierField field){ + public boolean isCaseSensitive(final IdentifierField field) { return field == null ? false : field.isCaseSensitive(caseSensitivity); } @Override - public String translate(StringConstant strConst) throws TranslationException{ + public String translate(StringConstant strConst) throws TranslationException { // Deal with the special escaping syntax of Postgres: /* A string containing characters to escape must be prefixed by an E. * Without this prefix, Potsgres does not escape the concerned characters and @@ -131,8 +133,16 @@ public class PostgreSQLTranslator extends JDBCTranslator { } @Override - public String translate(MathFunction fct) throws TranslationException{ - switch(fct.getType()){ + public String translate(Operation op) throws TranslationException { + if (op.getOperation() == OperationType.BIT_XOR) + return "(" + translate(op.getLeftOperand()) + " # " + translate(op.getRightOperand()) + ")"; + else + return super.translate(op); + } + + @Override + public String translate(MathFunction fct) throws TranslationException { + switch(fct.getType()) { case LOG: return "ln(" + ((fct.getNbParameters() >= 1) ? "CAST(" + translate(fct.getParameter(0)) + " AS numeric)" : "") + ")"; case LOG10: @@ -166,67 +176,67 @@ public class PostgreSQLTranslator extends JDBCTranslator { } @Override - public String translate(ExtractCoord extractCoord) throws TranslationException{ + public String translate(ExtractCoord extractCoord) throws TranslationException { return getDefaultADQLFunction(extractCoord); } @Override - public String translate(ExtractCoordSys extractCoordSys) throws TranslationException{ + public String translate(ExtractCoordSys extractCoordSys) throws TranslationException { return getDefaultADQLFunction(extractCoordSys); } @Override - public String translate(AreaFunction areaFunction) throws TranslationException{ + public String translate(AreaFunction areaFunction) throws TranslationException { return getDefaultADQLFunction(areaFunction); } @Override - public String translate(CentroidFunction centroidFunction) throws TranslationException{ + public String translate(CentroidFunction centroidFunction) throws TranslationException { return getDefaultADQLFunction(centroidFunction); } @Override - public String translate(DistanceFunction fct) throws TranslationException{ + public String translate(DistanceFunction fct) throws TranslationException { return getDefaultADQLFunction(fct); } @Override - public String translate(ContainsFunction fct) throws TranslationException{ + public String translate(ContainsFunction fct) throws TranslationException { return getDefaultADQLFunction(fct); } @Override - public String translate(IntersectsFunction fct) throws TranslationException{ + public String translate(IntersectsFunction fct) throws TranslationException { return getDefaultADQLFunction(fct); } @Override - public String translate(BoxFunction box) throws TranslationException{ + public String translate(BoxFunction box) throws TranslationException { return getDefaultADQLFunction(box); } @Override - public String translate(CircleFunction circle) throws TranslationException{ + public String translate(CircleFunction circle) throws TranslationException { return getDefaultADQLFunction(circle); } @Override - public String translate(PointFunction point) throws TranslationException{ + public String translate(PointFunction point) throws TranslationException { return getDefaultADQLFunction(point); } @Override - public String translate(PolygonFunction polygon) throws TranslationException{ + public String translate(PolygonFunction polygon) throws TranslationException { return getDefaultADQLFunction(polygon); } @Override - public String translate(RegionFunction region) throws TranslationException{ + public String translate(RegionFunction region) throws TranslationException { return getDefaultADQLFunction(region); } @Override - public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ + 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 null; @@ -236,10 +246,11 @@ public class PostgreSQLTranslator extends JDBCTranslator { // Extract the length parameter (always the first one): int lengthParam = DBType.NO_LENGTH; - if (params != null && params.length > 0){ - try{ + if (params != null && params.length > 0) { + try { lengthParam = Integer.parseInt(params[0]); - }catch(NumberFormatException nfe){} + } catch(NumberFormatException nfe) { + } } // SMALLINT @@ -284,11 +295,11 @@ public class PostgreSQLTranslator extends JDBCTranslator { } @Override - public String convertTypeToDB(final DBType type){ + public String convertTypeToDB(final DBType type) { if (type == null) return "VARCHAR"; - switch(type.type){ + switch(type.type) { case SMALLINT: case INTEGER: @@ -320,12 +331,12 @@ public class PostgreSQLTranslator extends JDBCTranslator { } @Override - public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException{ + public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException { throw new ParseException("Unsupported geometrical value! The value \"" + jdbcColValue + "\" can not be parsed as a region."); } @Override - public Object translateGeometryToDB(final Region region) throws ParseException{ + public Object translateGeometryToDB(final Region region) throws ParseException { throw new ParseException("Geometries can not be uploaded in the database in this implementation!"); } diff --git a/test/adql/db/TestSubQueries.java b/test/adql/db/TestSubQueries.java index afc58cbb996fa393d730ab643fb9090eb661d08c..ff9e8283eb96aa48832e1ba7993b14319ef68b28 100644 --- a/test/adql/db/TestSubQueries.java +++ b/test/adql/db/TestSubQueries.java @@ -79,7 +79,7 @@ public class TestSubQueries { adqlParser.setQueryChecker(new DBChecker(esaTables)); ADQLQuery query = adqlParser.parseQuery("SELECT t.* FROM (SELECT (ra+ra_error) AS x, (dec+dec_error) AS Y, pmra AS \"ProperMotion\" FROM table2) AS t"); - assertEquals("SELECT \"t\".\"x\" AS \"x\",\"t\".\"y\" AS \"y\",\"t\".\"ProperMotion\" AS \"ProperMotion\"\nFROM (SELECT (\"public\".\"table2\".\"ra\"+\"public\".\"table2\".\"ra_error\") AS \"x\" , (\"public\".\"table2\".\"dec\"+\"public\".\"table2\".\"dec_error\") AS \"y\" , \"public\".\"table2\".\"pmra\" AS \"ProperMotion\"\nFROM \"public\".\"table2\") AS \"t\"", (new PostgreSQLTranslator()).translate(query)); + assertEquals("SELECT \"t\".\"x\" AS \"x\",\"t\".\"y\" AS \"y\",\"t\".\"ProperMotion\" AS \"ProperMotion\"\nFROM (SELECT ((\"public\".\"table2\".\"ra\"+\"public\".\"table2\".\"ra_error\")) AS \"x\" , ((\"public\".\"table2\".\"dec\"+\"public\".\"table2\".\"dec_error\")) AS \"y\" , \"public\".\"table2\".\"pmra\" AS \"ProperMotion\"\nFROM \"public\".\"table2\") AS \"t\"", (new PostgreSQLTranslator()).translate(query)); } catch(Exception ex) { ex.printStackTrace(System.err); fail("No error expected! (see console for more details)"); diff --git a/test/adql/parser/TestADQLParser.java b/test/adql/parser/TestADQLParser.java index 2e001462d9a67361a83dc0fe5bc290235ad45da7..f46d1e1d1e862f0350d6a53430153833deb9dbcb 100644 --- a/test/adql/parser/TestADQLParser.java +++ b/test/adql/parser/TestADQLParser.java @@ -26,6 +26,10 @@ import adql.parser.grammar.Token; import adql.query.ADQLQuery; import adql.query.from.ADQLJoin; import adql.query.from.ADQLTable; +import adql.query.operand.ADQLOperand; +import adql.query.operand.BitNotOperand; +import adql.query.operand.Operation; +import adql.query.operand.OperationType; import adql.query.operand.StringConstant; import adql.query.operand.function.geometry.CircleFunction; import adql.query.operand.function.geometry.ContainsFunction; @@ -51,6 +55,88 @@ public class TestADQLParser { public void tearDown() throws Exception { } + @Test + public void testOperatorsPrecedence() { + + ADQLParser parser = new ADQLParser(ADQLVersion.V2_1); + + // CASE: Check the generated tree (i.e. precedence of operators): + try { + ADQLQuery query = parser.parseQuery("SELECT ~3-1|2*5^6/1+2 FROM foo"); + assertEquals("SELECT ~3-1|2*5^6/1+2\nFROM foo", query.toADQL()); + // top operand = OR( ~3-1 , 2*5^6/1+2 ) + ADQLOperand topOp = query.getSelect().get(0).getOperand(); + assertEquals(Operation.class, topOp.getClass()); + assertEquals(OperationType.BIT_OR, ((Operation)topOp).getOperation()); + assertEquals("~3-1", ((Operation)topOp).getLeftOperand().toADQL()); + assertEquals("2*5^6/1+2", ((Operation)topOp).getRightOperand().toADQL()); + // left operand = SUB( ~3 , 1 ) + ADQLOperand op = ((Operation)topOp).getLeftOperand(); + assertEquals(Operation.class, op.getClass()); + assertEquals(OperationType.SUB, ((Operation)op).getOperation()); + assertEquals("~3", ((Operation)op).getLeftOperand().toADQL()); + assertEquals("1", ((Operation)op).getRightOperand().toADQL()); + // left operand = BIT_NOT( 3 ) + op = ((Operation)op).getLeftOperand(); + assertEquals(BitNotOperand.class, op.getClass()); + assertEquals("3", ((BitNotOperand)op).getOperand().toADQL()); + // right operand = BIT_XOR( 2*5 , 6/1+2 ) + topOp = ((Operation)topOp).getRightOperand(); + assertEquals(Operation.class, topOp.getClass()); + assertEquals(OperationType.BIT_XOR, ((Operation)topOp).getOperation()); + assertEquals("2*5", ((Operation)topOp).getLeftOperand().toADQL()); + assertEquals("6/1+2", ((Operation)topOp).getRightOperand().toADQL()); + // left operand = MULT( 2 , 5 ) + op = ((Operation)topOp).getLeftOperand(); + assertEquals(Operation.class, op.getClass()); + assertEquals(OperationType.MULT, ((Operation)op).getOperation()); + assertEquals("2", ((Operation)op).getLeftOperand().toADQL()); + assertEquals("5", ((Operation)op).getRightOperand().toADQL()); + // right operand = SUM( 6/1 , 2 ) + op = ((Operation)topOp).getRightOperand(); + assertEquals(Operation.class, op.getClass()); + assertEquals(OperationType.SUM, ((Operation)op).getOperation()); + assertEquals("6/1", ((Operation)op).getLeftOperand().toADQL()); + assertEquals("2", ((Operation)op).getRightOperand().toADQL()); + // left operand = DIV( 6 , 1 ) + op = ((Operation)op).getLeftOperand(); + assertEquals(Operation.class, op.getClass()); + assertEquals(OperationType.DIV, ((Operation)op).getOperation()); + assertEquals("6", ((Operation)op).getLeftOperand().toADQL()); + assertEquals("1", ((Operation)op).getRightOperand().toADQL()); + + } catch(Exception ex) { + ex.printStackTrace(); + fail("Unexpected error with valid operations! (see console for more details)"); + } + } + + @Test + public void testBitwiseOperation() { + + // CASE: No bitwise operation in ADQL-2.0 + ADQLParser parser = new ADQLParser(ADQLVersion.V2_0); + try { + parser.parseQuery("SELECT 3|2 FROM foo"); + fail("Bitwise operations should not be allowed with ADQL-2.0!"); + } catch(Exception ex) { + assertEquals(ParseException.class, ex.getClass()); + assertEquals(" Encountered \"|\". Was expecting one of: \",\" \"FROM\" \"AS\" \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> " + System.getProperty("line.separator", "\n") + "(HINT: \"|\" bitwise operations are not supported in ADQL-2.0. You should migrate your ADQL parser to support at least ADQL-2.1.)", ex.getMessage()); + } + + // CASE: Bitwise operations allowed in ADQL-2.1 + parser = new ADQLParser(ADQLVersion.V2_1); + try { + assertEquals("SELECT 3|2\nFROM foo", parser.parseQuery("SELECT 3|2 FROM foo").toADQL()); + assertEquals("SELECT 0xF&5\nFROM foo", parser.parseQuery("SELECT 0xF &5 FROM foo").toADQL()); + assertEquals("SELECT 67^45\nFROM foo", parser.parseQuery("SELECT 67 ^ 45 FROM foo").toADQL()); + assertEquals("SELECT ~0x3 , ~0x4 , ~3\nFROM foo", parser.parseQuery("SELECT ~ 0x3, ~0x4, ~ 3 FROM foo").toADQL()); + } catch(Exception ex) { + ex.printStackTrace(); + fail("Unexpected error with valid bitwise operations! (see console for more details)"); + } + } + @Test public void testHexadecimal() { @@ -61,7 +147,7 @@ public class TestADQLParser { fail("Hexadecimal values should not be allowed with ADQL-2.0!"); } catch(Exception ex) { assertEquals(ParseException.class, ex.getClass()); - assertEquals("Invalid ADQL regular identifier: \"0xF\"! HINT: hexadecimal values are not supported in ADQL-2.0. You should change the grammar version of the ADQL parser to at least ADQL-2.1.", ex.getMessage()); + assertEquals("Invalid ADQL regular identifier: \"0xF\"!" + System.getProperty("line.separator", "\n") + "(HINT: hexadecimal values are not supported in ADQL-2.0. You should upgrade your ADQL parser to support at least ADQL-2.1.)", ex.getMessage()); } // CASE: Hexadecimal allowed in ADQL-2.1 @@ -770,7 +856,7 @@ public class TestADQLParser { fail("No known token is provided. A ParseException was expected."); } catch(Exception ex) { assertEquals(ParseException.class, ex.getClass()); - assertEquals("Incorrect character encountered at l.1, c.3: \"\\u00e9\" ('é'), after : \"\"! Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it).", ex.getMessage()); + assertEquals("Incorrect character encountered at l.1, c.3: \"\\u00e9\" ('é'), after : \"\"!" + System.getProperty("line.separator", "\n") + "Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it).", ex.getMessage()); } // TEST: correct list of token => ok diff --git a/test/adql/translator/TestJDBCTranslator.java b/test/adql/translator/TestJDBCTranslator.java index fe035ce5824dea8b8bde32c3f4eb89fcacc25792..acf049942f9e041a48c68b53846ec5209fe362dc 100644 --- a/test/adql/translator/TestJDBCTranslator.java +++ b/test/adql/translator/TestJDBCTranslator.java @@ -12,9 +12,12 @@ import adql.db.STCS.Region; import adql.parser.ADQLParser; import adql.parser.ADQLParser.ADQLVersion; import adql.parser.grammar.ParseException; +import adql.query.ADQLQuery; import adql.query.IdentifierField; import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; +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.geometry.AreaFunction; @@ -36,6 +39,23 @@ public class TestJDBCTranslator { public void setUp() throws Exception { } + @Test + public void testTranslateComplexNumericOperation() { + JDBCTranslator tr = new AJDBCTranslator(); + ADQLParser parser = new ADQLParser(ADQLVersion.V2_1); + + // CASE: Check the applied operators precedence while translating: + try { + ADQLQuery query = parser.parseQuery("SELECT ~3-1|2*5^6/1+2 FROM foo"); + assertEquals("SELECT ~3-1|2*5^6/1+2\nFROM foo", query.toADQL()); + assertEquals(Operation.class, query.getSelect().get(0).getOperand().getClass()); + assertEquals("(((~3)-1)|((2*5)^((6/1)+2)))", tr.translate(query.getSelect().get(0).getOperand())); + } catch(Exception ex) { + ex.printStackTrace(); + fail("Unexpected error with valid operations! (see console for more details)"); + } + } + @Test public void testTranslateOffset() { JDBCTranslator tr = new AJDBCTranslator(); @@ -72,7 +92,7 @@ public class TestJDBCTranslator { try { assertEquals("SELECT 15 AS \"0xF\"\nFROM foo", tr.translate(parser.parseQuery("Select 0xF From foo"))); - assertEquals("SELECT 15*2 AS \"MULT\"\nFROM foo", tr.translate(parser.parseQuery("Select 0xF*2 From foo"))); + assertEquals("SELECT (15*2) AS \"MULT\"\nFROM foo", tr.translate(parser.parseQuery("Select 0xF*2 From foo"))); assertEquals("SELECT -15 AS \"NEG_0xF\"\nFROM foo", tr.translate(parser.parseQuery("Select -0xF From foo"))); } catch(ParseException pe) { @@ -89,10 +109,10 @@ public class TestJDBCTranslator { JDBCTranslator tr = new AJDBCTranslator(); /* Ensure the translation from ADQL to SQL of strings is correct ; - * particularly, ' should be escaped otherwise it would mean the end of a string in SQL - *(the way to escape a such character is by doubling the character '): */ + * particularly, ' should be escaped otherwise it would mean the end of + * a string in SQL (the way to escape a such character is by doubling + * the character '): */ try{ -==== BASE ==== assertEquals("'SQL''s translation'", tr.translate(new StringConstant("SQL's translation"))); } catch(TranslationException e) { e.printStackTrace(System.err);