Skip to content
Snippets Groups Projects
Commit 0bad1f8b authored by Grégory Mantelet's avatar Grégory Mantelet
Browse files

[ADQL] Support bitwise operators + Fix wrong hexadecimal conversion (only for

negative values).

The implemented operators precedence is:

```
[~-](unary) >> [*/] >> [+-] >> [^&|]
```

_**Example:** `~3-1|2*5^6/1+2 = ((~3)-1)|((2*5)^((6/1)+2)) = -5`_
parent c7bede57
Branches
No related tags found
No related merge requests found
Showing
with 518 additions and 93 deletions
...@@ -51,6 +51,7 @@ import adql.query.from.OuterJoin; ...@@ -51,6 +51,7 @@ import adql.query.from.OuterJoin;
import adql.query.from.OuterJoin.OuterType; import adql.query.from.OuterJoin.OuterType;
import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand; import adql.query.operand.ADQLOperand;
import adql.query.operand.BitNotOperand;
import adql.query.operand.Concatenation; import adql.query.operand.Concatenation;
import adql.query.operand.NegativeOperand; import adql.query.operand.NegativeOperand;
import adql.query.operand.NumericConstant; import adql.query.operand.NumericConstant;
...@@ -248,6 +249,21 @@ public class ADQLQueryFactory { ...@@ -248,6 +249,21 @@ public class ADQLQueryFactory {
return new NegativeOperand(opToNegativate); 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 { public Concatenation createConcatenation() throws Exception {
return new Concatenation(); return new Concatenation();
} }
......
...@@ -30,6 +30,8 @@ import java.util.Set; ...@@ -30,6 +30,8 @@ import java.util.Set;
import adql.db.FunctionDef; import adql.db.FunctionDef;
import adql.query.ClauseOffset; import adql.query.ClauseOffset;
import adql.query.constraint.ComparisonOperator; 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.AreaFunction;
import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.BoxFunction;
import adql.query.operand.function.geometry.CentroidFunction; import adql.query.operand.function.geometry.CentroidFunction;
...@@ -147,7 +149,7 @@ import adql.query.operand.function.string.LowerFunction; ...@@ -147,7 +149,7 @@ import adql.query.operand.function.string.LowerFunction;
* </ul> * </ul>
* *
* @author Gr&eacute;gory Mantelet (CDS) * @author Gr&eacute;gory Mantelet (CDS)
* @version 2.0 (07/2019) * @version 2.0 (08/2019)
* @since 2.0 * @since 2.0
*/ */
public class FeatureSet implements Iterable<LanguageFeature> { public class FeatureSet implements Iterable<LanguageFeature> {
...@@ -584,14 +586,7 @@ 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 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 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*/
/** All standard features available. /** All standard features available.
* <p> * <p>
...@@ -603,7 +598,7 @@ public class FeatureSet implements Iterable<LanguageFeature> { ...@@ -603,7 +598,7 @@ public class FeatureSet implements Iterable<LanguageFeature> {
* <p><i><b>Important note:</b> * <p><i><b>Important note:</b>
* All of them must be optional and must have a type. * All of them must be optional and must have a type.
* </i></p> */ * </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. * List all available language features.
......
...@@ -110,9 +110,9 @@ public abstract class ADQLGrammarBase implements ADQLGrammar { ...@@ -110,9 +110,9 @@ public abstract class ADQLGrammarBase implements ADQLGrammar {
else if (!isRegularIdentifier(token.image)) { else if (!isRegularIdentifier(token.image)) {
String message = "Invalid ADQL regular identifier: \u005c"" + token.image + "\u005c"!"; String message = "Invalid ADQL regular identifier: \u005c"" + token.image + "\u005c"!";
if (getVersion() == ADQLVersion.V2_0 && token.image.matches("0[Xx][0-9a-fA-F]+")) 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 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)); throw new ParseException(message, new TextPosition(token));
} }
} }
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
* Modified by Gr&eacute;gory Mantelet (CDS), on Aug. 2019 * Modified by Gr&eacute;gory Mantelet (CDS), on Aug. 2019
* Modifications: * Modifications:
* - change the package name * - 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 /!\ * /!\ DO NOT RE-GENERATE THIS FILE /!\
* In case of re-generation, replace it by ParseException.java.backup (but maybe * In case of re-generation, replace it by ParseException.java.backup (but maybe
...@@ -36,6 +38,7 @@ ...@@ -36,6 +38,7 @@
*/ */
package adql.parser.grammar; package adql.parser.grammar;
import adql.parser.ADQLParser.ADQLVersion;
import adql.query.TextPosition; import adql.query.TextPosition;
/** /**
...@@ -46,6 +49,10 @@ import adql.query.TextPosition; ...@@ -46,6 +49,10 @@ import adql.query.TextPosition;
* *
* You can modify this class to customize your error reporting * You can modify this class to customize your error reporting
* mechanisms so long as you retain the public fields. * mechanisms so long as you retain the public fields.
*
* @version 2.0 (08/2019)
* @author JavaCC
* @author Gr&eacute;gory Mantelet (CDS)
*/ */
public class ParseException extends Exception { public class ParseException extends Exception {
...@@ -101,9 +108,9 @@ public class ParseException extends Exception { ...@@ -101,9 +108,9 @@ public class ParseException extends Exception {
private final static String buildExpandedMessage(final TokenMgrError err) { private final static String buildExpandedMessage(final TokenMgrError err) {
if (err.getMessage().indexOf("<EOF>") > 0) 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 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 { ...@@ -190,9 +197,11 @@ public class ParseException extends Exception {
if (maxSize == 1) { if (maxSize == 1) {
tok = currentToken.next; tok = currentToken.next;
if (tok.adqlReserved) 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) 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(); return msg.toString();
...@@ -239,7 +248,7 @@ public class ParseException extends Exception { ...@@ -239,7 +248,7 @@ public class ParseException extends Exception {
/** /**
* The end of line string for this machine. * 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 * Used to convert raw characters to their escaped version
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
* Modified by Gr&eacute;gory Mantelet (CDS), on Aug. 2019 * Modified by Gr&eacute;gory Mantelet (CDS), on Aug. 2019
* Modifications: * Modifications:
* - change the package name * - 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 /!\ * /!\ DO NOT RE-GENERATE THIS FILE /!\
* In case of re-generation, replace it by ParseException.java.backup (but maybe * In case of re-generation, replace it by ParseException.java.backup (but maybe
...@@ -36,6 +38,7 @@ ...@@ -36,6 +38,7 @@
*/ */
package adql.parser.grammar; package adql.parser.grammar;
import adql.parser.ADQLParser.ADQLVersion;
import adql.query.TextPosition; import adql.query.TextPosition;
/** /**
...@@ -46,6 +49,10 @@ import adql.query.TextPosition; ...@@ -46,6 +49,10 @@ import adql.query.TextPosition;
* *
* You can modify this class to customize your error reporting * You can modify this class to customize your error reporting
* mechanisms so long as you retain the public fields. * mechanisms so long as you retain the public fields.
*
* @version 2.0 (08/2019)
* @author JavaCC
* @author Gr&eacute;gory Mantelet (CDS)
*/ */
public class ParseException extends Exception { public class ParseException extends Exception {
...@@ -101,9 +108,9 @@ public class ParseException extends Exception { ...@@ -101,9 +108,9 @@ public class ParseException extends Exception {
private final static String buildExpandedMessage(final TokenMgrError err) { private final static String buildExpandedMessage(final TokenMgrError err) {
if (err.getMessage().indexOf("<EOF>") > 0) 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 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 { ...@@ -190,9 +197,11 @@ public class ParseException extends Exception {
if (maxSize == 1) { if (maxSize == 1) {
tok = currentToken.next; tok = currentToken.next;
if (tok.adqlReserved) 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) 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(); return msg.toString();
...@@ -239,7 +248,7 @@ public class ParseException extends Exception { ...@@ -239,7 +248,7 @@ public class ParseException extends Exception {
/** /**
* The end of line string for this machine. * 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 * Used to convert raw characters to their escaped version
......
...@@ -229,6 +229,16 @@ TOKEN : { ...@@ -229,6 +229,16 @@ TOKEN : {
| < DIVIDE: "/" > | < DIVIDE: "/" >
} }
/* **************** */
/* Binary operators */
/* **************** */
TOKEN : {
< BIT_AND: "&" >
| < BIT_OR: "|" >
| < BIT_XOR: "^" >
| < BIT_NOT: "~" >
}
/* ******************** */ /* ******************** */
/* Comparison operators */ /* Comparison operators */
/* ******************** */ /* ******************** */
...@@ -396,7 +406,7 @@ TOKEN : { ...@@ -396,7 +406,7 @@ TOKEN : {
TOKEN : { TOKEN : {
< REGULAR_IDENTIFIER_CANDIDATE: ((<Letter>)+ (<DIGIT> | <Letter>)* | (<DIGIT>)+ <Letter> (<DIGIT> | <Letter>)*) > < REGULAR_IDENTIFIER_CANDIDATE: ((<Letter>)+ (<DIGIT> | <Letter>)* | (<DIGIT>)+ <Letter> (<DIGIT> | <Letter>)*) >
| < #Letter: ["a"-"z","A"-"Z","_","?","$","@","^","#","`","~","[","]","{","}"] > | < #Letter: ["a"-"z","A"-"Z","_","?","$","@","#","`","[","]","{","}"] >
} }
/* ########## */ /* ########## */
......
...@@ -232,6 +232,16 @@ TOKEN : { ...@@ -232,6 +232,16 @@ TOKEN : {
| < DIVIDE: "/" > | < DIVIDE: "/" >
} }
/* **************** */
/* Binary operators */
/* **************** */
TOKEN : {
< BIT_AND: "&" >
| < BIT_OR: "|" >
| < BIT_XOR: "^" >
| < BIT_NOT: "~" >
}
/* ******************** */ /* ******************** */
/* Comparison operators */ /* Comparison operators */
/* ******************** */ /* ******************** */
...@@ -409,7 +419,7 @@ TOKEN : { ...@@ -409,7 +419,7 @@ TOKEN : {
TOKEN : { TOKEN : {
< REGULAR_IDENTIFIER_CANDIDATE: ((<Letter>)+ (<DIGIT> | <Letter>)* | (<DIGIT>)+ <Letter> (<DIGIT> | <Letter>)*) > < 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 ...@@ -865,7 +875,7 @@ ADQLOperand StringValueExpressionPrimary(): {StringConstant expr; ADQLColumn col
ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; } { ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; } {
try{ 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(<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(<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() | LOOKAHEAD(<REGULAR_IDENTIFIER_CANDIDATE> <LEFT_PAR>) valueExpr=UserDefinedFunction()
...@@ -890,8 +900,25 @@ ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right; ...@@ -890,8 +900,25 @@ ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right;
} }
} }
ADQLOperand NumericExpression(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} { ADQLOperand NumericExpression(): { Token operator = null; ADQLOperand leftOp, rightOp = null; } {
(leftOp=NumericTerm() ((sign=<PLUS> | sign=<MINUS>) rightOp=NumericExpression())?) (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) if (sign == null)
return leftOp; return leftOp;
...@@ -907,8 +934,8 @@ ADQLOperand NumericExpression(): {Token sign=null; ADQLOperand leftOp, rightOp=n ...@@ -907,8 +934,8 @@ ADQLOperand NumericExpression(): {Token sign=null; ADQLOperand leftOp, rightOp=n
} }
} }
ADQLOperand NumericTerm(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} { ADQLOperand Term(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} {
(leftOp=Factor() ((sign=<ASTERISK> | sign=<DIVIDE>) rightOp=NumericTerm())?) (leftOp=Factor() ((sign=<ASTERISK> | sign=<DIVIDE>) rightOp=Term())?)
{ {
if (sign == null) if (sign == null)
return leftOp; return leftOp;
...@@ -924,29 +951,27 @@ ADQLOperand NumericTerm(): {Token sign=null; ADQLOperand leftOp, rightOp=null;} ...@@ -924,29 +951,27 @@ 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()) (LOOKAHEAD(2) op=NumericFunction() | op=NumericValueExpressionPrimary())
) )
{ {
if (negative){
try { try {
TextPosition position = op.getPosition(); if (bitNot != null) {
op = queryFactory.createNegativeOperand(op); BitNotOperand bitNotOperand = queryFactory.createBitNotOperand(op);
NegativeOperand negativeOp = (NegativeOperand)op; bitNotOperand.setPosition(new TextPosition(new TextPosition(bitNot), op.getPosition()));
if (minusSign != null) return bitNotOperand;
negativeOp.setPosition(new TextPosition(minusSign.beginLine, minusSign.beginColumn, position.endLine, position.endColumn)); } else if (minusSign != null) {
else NegativeOperand negativeOp = queryFactory.createNegativeOperand(op);
negativeOp.setPosition(position); negativeOp.setPosition(new TextPosition(new TextPosition(minusSign), op.getPosition()));
return negativeOp;
} else
return op;
}catch(Exception ex){ }catch(Exception ex){
throw generateParseException(ex); throw generateParseException(ex);
} }
} }
return op;
}
} }
ADQLOperand StringExpression(): {ADQLOperand leftOp; ADQLOperand rightOp = null;} { ADQLOperand StringExpression(): {ADQLOperand leftOp; ADQLOperand rightOp = null;} {
......
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&eacute;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();
}
}
...@@ -29,10 +29,10 @@ import adql.query.TextPosition; ...@@ -29,10 +29,10 @@ import adql.query.TextPosition;
/** /**
* It represents a simple numeric operation (sum, difference, multiplication and * It represents a simple numeric operation (sum, difference, multiplication and
* division). * division) or a bitwise operation (binary AND, OR or XOR).
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (07/2019) * @version 2.0 (08/2019)
* *
* @see OperationType * @see OperationType
*/ */
......
package adql.query.operand; package adql.query.operand;
import adql.parser.feature.LanguageFeature;
import adql.query.operand.function.SQLFunction;
/* /*
* This file is part of ADQLLibrary. * This file is part of ADQLLibrary.
* *
...@@ -22,16 +19,28 @@ import adql.query.operand.function.SQLFunction; ...@@ -22,16 +19,28 @@ import adql.query.operand.function.SQLFunction;
* Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS) * 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. * Type of possible simple numeric operations.
* *
* @author Gr&eacute;gory Mantelet (CDS) * @author Gr&eacute;gory Mantelet (CDS)
* @version 2.0 (07/2019) * @version 2.0 (08/2019)
* *
* @see Operation * @see Operation
*/ */
public enum OperationType { 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. /** Description of the ADQL Feature based on this type.
* @since 2.0 */ * @since 2.0 */
...@@ -39,6 +48,9 @@ public enum OperationType { ...@@ -39,6 +48,9 @@ public enum OperationType {
/** @since 2.0 */ /** @since 2.0 */
private OperationType() { private OperationType() {
if (this.name().startsWith("BIT_"))
FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_BITWISE, this.name(), true);
else
FEATURE = new LanguageFeature(null, this.name(), false); FEATURE = new LanguageFeature(null, this.name(), false);
} }
...@@ -61,7 +73,7 @@ public enum OperationType { ...@@ -61,7 +73,7 @@ public enum OperationType {
} }
public static String[] getOperators() { 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 { public static OperationType getOperator(String str) throws UnsupportedOperationException {
...@@ -73,6 +85,12 @@ public enum OperationType { ...@@ -73,6 +85,12 @@ public enum OperationType {
return MULT; return MULT;
else if (str.equalsIgnoreCase("/")) else if (str.equalsIgnoreCase("/"))
return DIV; return DIV;
else if (str.equalsIgnoreCase("&"))
return BIT_AND;
else if (str.equalsIgnoreCase("|"))
return BIT_OR;
else if (str.equalsIgnoreCase("^"))
return BIT_XOR;
else else
throw new UnsupportedOperationException("Numeric operation unknown: \"" + str + "\" !"); throw new UnsupportedOperationException("Numeric operation unknown: \"" + str + "\" !");
} }
...@@ -92,6 +110,12 @@ public enum OperationType { ...@@ -92,6 +110,12 @@ public enum OperationType {
return "*"; return "*";
case DIV: case DIV:
return "/"; return "/";
case BIT_AND:
return "&";
case BIT_OR:
return "|";
case BIT_XOR:
return "^";
default: default:
return "???"; return "???";
} }
......
...@@ -41,6 +41,7 @@ import adql.query.from.ADQLTable; ...@@ -41,6 +41,7 @@ import adql.query.from.ADQLTable;
import adql.query.from.FromContent; import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand; import adql.query.operand.ADQLOperand;
import adql.query.operand.BitNotOperand;
import adql.query.operand.Concatenation; import adql.query.operand.Concatenation;
import adql.query.operand.NegativeOperand; import adql.query.operand.NegativeOperand;
import adql.query.operand.NumericConstant; import adql.query.operand.NumericConstant;
...@@ -112,6 +113,9 @@ public interface ADQLTranslator { ...@@ -112,6 +113,9 @@ public interface ADQLTranslator {
public String translate(NegativeOperand negOp) throws TranslationException; 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(NumericConstant numConst) throws TranslationException;
public String translate(StringConstant strConst) throws TranslationException; public String translate(StringConstant strConst) throws TranslationException;
......
...@@ -52,6 +52,7 @@ import adql.query.from.ADQLTable; ...@@ -52,6 +52,7 @@ import adql.query.from.ADQLTable;
import adql.query.from.FromContent; import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand; import adql.query.operand.ADQLOperand;
import adql.query.operand.BitNotOperand;
import adql.query.operand.Concatenation; import adql.query.operand.Concatenation;
import adql.query.operand.NegativeOperand; import adql.query.operand.NegativeOperand;
import adql.query.operand.NumericConstant; import adql.query.operand.NumericConstant;
...@@ -673,6 +674,8 @@ public abstract class JDBCTranslator implements ADQLTranslator { ...@@ -673,6 +674,8 @@ public abstract class JDBCTranslator implements ADQLTranslator {
return translate((Concatenation)op); return translate((Concatenation)op);
else if (op instanceof NegativeOperand) else if (op instanceof NegativeOperand)
return translate((NegativeOperand)op); return translate((NegativeOperand)op);
else if (op instanceof BitNotOperand)
return translate((BitNotOperand)op);
else if (op instanceof NumericConstant) else if (op instanceof NumericConstant)
return translate((NumericConstant)op); return translate((NumericConstant)op);
else if (op instanceof StringConstant) else if (op instanceof StringConstant)
...@@ -727,11 +730,24 @@ public abstract class JDBCTranslator implements ADQLTranslator { ...@@ -727,11 +730,24 @@ public abstract class JDBCTranslator implements ADQLTranslator {
return "-" + translate(negOp.getOperand()); return "-" + translate(negOp.getOperand());
} }
@Override
public String translate(BitNotOperand bitNotOp) throws TranslationException {
return "(~" + translate(bitNotOp.getOperand()) + ")";
}
@Override @Override
public String translate(NumericConstant numConst) throws TranslationException { public String translate(NumericConstant numConst) throws TranslationException {
if (numConst.isHexadecimal()) { if (numConst.isHexadecimal()) {
try { 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) { } catch(NumberFormatException nfe) {
throw new TranslationException("Impossible to evaluate the given hexadecimal expression: \"" + numConst.getValue() + "\"!", nfe); throw new TranslationException("Impossible to evaluate the given hexadecimal expression: \"" + numConst.getValue() + "\"!", nfe);
} }
...@@ -751,7 +767,7 @@ public abstract class JDBCTranslator implements ADQLTranslator { ...@@ -751,7 +767,7 @@ public abstract class JDBCTranslator implements ADQLTranslator {
@Override @Override
public String translate(Operation op) throws TranslationException { 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()) + ")";
} }
/* ************************ */ /* ************************ */
......
...@@ -28,6 +28,7 @@ import adql.query.IdentifierField; ...@@ -28,6 +28,7 @@ import adql.query.IdentifierField;
import adql.query.constraint.Comparison; import adql.query.constraint.Comparison;
import adql.query.operand.ADQLOperand; import adql.query.operand.ADQLOperand;
import adql.query.operand.Concatenation; import adql.query.operand.Concatenation;
import adql.query.operand.Operation;
import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.AreaFunction;
import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.BoxFunction;
import adql.query.operand.function.geometry.CentroidFunction; import adql.query.operand.function.geometry.CentroidFunction;
...@@ -137,6 +138,18 @@ public class MySQLTranslator extends JDBCTranslator { ...@@ -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 @Override
public String translate(Comparison comp) throws TranslationException { public String translate(Comparison comp) throws TranslationException {
switch(comp.getOperator()) { switch(comp.getOperator()) {
......
...@@ -16,7 +16,7 @@ package adql.translator; ...@@ -16,7 +16,7 @@ package adql.translator;
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. * 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) * Astronomisches Rechen Institut (ARI)
*/ */
...@@ -25,6 +25,8 @@ import adql.db.DBType.DBDatatype; ...@@ -25,6 +25,8 @@ import adql.db.DBType.DBDatatype;
import adql.db.STCS.Region; import adql.db.STCS.Region;
import adql.parser.grammar.ParseException; import adql.parser.grammar.ParseException;
import adql.query.IdentifierField; import adql.query.IdentifierField;
import adql.query.operand.Operation;
import adql.query.operand.OperationType;
import adql.query.operand.StringConstant; import adql.query.operand.StringConstant;
import adql.query.operand.function.ADQLFunction; import adql.query.operand.function.ADQLFunction;
import adql.query.operand.function.MathFunction; import adql.query.operand.function.MathFunction;
...@@ -64,7 +66,7 @@ import adql.query.operand.function.geometry.RegionFunction; ...@@ -64,7 +66,7 @@ import adql.query.operand.function.geometry.RegionFunction;
* </i></p> * </i></p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 1.4 (08/2016) * @version 2.0 (08/2019)
* *
* @see PgSphereTranslator * @see PgSphereTranslator
*/ */
...@@ -130,6 +132,14 @@ public class PostgreSQLTranslator extends JDBCTranslator { ...@@ -130,6 +132,14 @@ public class PostgreSQLTranslator extends JDBCTranslator {
return super.translate(strConst); return super.translate(strConst);
} }
@Override
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 @Override
public String translate(MathFunction fct) throws TranslationException { public String translate(MathFunction fct) throws TranslationException {
switch(fct.getType()) { switch(fct.getType()) {
...@@ -239,7 +249,8 @@ public class PostgreSQLTranslator extends JDBCTranslator { ...@@ -239,7 +249,8 @@ public class PostgreSQLTranslator extends JDBCTranslator {
if (params != null && params.length > 0) { if (params != null && params.length > 0) {
try { try {
lengthParam = Integer.parseInt(params[0]); lengthParam = Integer.parseInt(params[0]);
}catch(NumberFormatException nfe){} } catch(NumberFormatException nfe) {
}
} }
// SMALLINT // SMALLINT
......
...@@ -79,7 +79,7 @@ public class TestSubQueries { ...@@ -79,7 +79,7 @@ public class TestSubQueries {
adqlParser.setQueryChecker(new DBChecker(esaTables)); 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"); 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) { } catch(Exception ex) {
ex.printStackTrace(System.err); ex.printStackTrace(System.err);
fail("No error expected! (see console for more details)"); fail("No error expected! (see console for more details)");
......
...@@ -26,6 +26,10 @@ import adql.parser.grammar.Token; ...@@ -26,6 +26,10 @@ import adql.parser.grammar.Token;
import adql.query.ADQLQuery; import adql.query.ADQLQuery;
import adql.query.from.ADQLJoin; import adql.query.from.ADQLJoin;
import adql.query.from.ADQLTable; 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.StringConstant;
import adql.query.operand.function.geometry.CircleFunction; import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.ContainsFunction; import adql.query.operand.function.geometry.ContainsFunction;
...@@ -51,6 +55,88 @@ public class TestADQLParser { ...@@ -51,6 +55,88 @@ public class TestADQLParser {
public void tearDown() throws Exception { 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 @Test
public void testHexadecimal() { public void testHexadecimal() {
...@@ -61,7 +147,7 @@ public class TestADQLParser { ...@@ -61,7 +147,7 @@ public class TestADQLParser {
fail("Hexadecimal values should not be allowed with ADQL-2.0!"); fail("Hexadecimal values should not be allowed with ADQL-2.0!");
} catch(Exception ex) { } catch(Exception ex) {
assertEquals(ParseException.class, ex.getClass()); 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 // CASE: Hexadecimal allowed in ADQL-2.1
...@@ -770,7 +856,7 @@ public class TestADQLParser { ...@@ -770,7 +856,7 @@ public class TestADQLParser {
fail("No known token is provided. A ParseException was expected."); fail("No known token is provided. A ParseException was expected.");
} catch(Exception ex) { } catch(Exception ex) {
assertEquals(ParseException.class, ex.getClass()); 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 // TEST: correct list of token => ok
......
...@@ -12,9 +12,12 @@ import adql.db.STCS.Region; ...@@ -12,9 +12,12 @@ import adql.db.STCS.Region;
import adql.parser.ADQLParser; import adql.parser.ADQLParser;
import adql.parser.ADQLParser.ADQLVersion; import adql.parser.ADQLParser.ADQLVersion;
import adql.parser.grammar.ParseException; import adql.parser.grammar.ParseException;
import adql.query.ADQLQuery;
import adql.query.IdentifierField; import adql.query.IdentifierField;
import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand; import adql.query.operand.ADQLOperand;
import adql.query.operand.NumericConstant;
import adql.query.operand.Operation;
import adql.query.operand.StringConstant; import adql.query.operand.StringConstant;
import adql.query.operand.function.DefaultUDF; import adql.query.operand.function.DefaultUDF;
import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.AreaFunction;
...@@ -36,6 +39,23 @@ public class TestJDBCTranslator { ...@@ -36,6 +39,23 @@ public class TestJDBCTranslator {
public void setUp() throws Exception { 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 @Test
public void testTranslateOffset() { public void testTranslateOffset() {
JDBCTranslator tr = new AJDBCTranslator(); JDBCTranslator tr = new AJDBCTranslator();
...@@ -72,7 +92,7 @@ public class TestJDBCTranslator { ...@@ -72,7 +92,7 @@ public class TestJDBCTranslator {
try { try {
assertEquals("SELECT 15 AS \"0xF\"\nFROM foo", tr.translate(parser.parseQuery("Select 0xF From foo"))); 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"))); assertEquals("SELECT -15 AS \"NEG_0xF\"\nFROM foo", tr.translate(parser.parseQuery("Select -0xF From foo")));
} catch(ParseException pe) { } catch(ParseException pe) {
...@@ -89,10 +109,10 @@ public class TestJDBCTranslator { ...@@ -89,10 +109,10 @@ public class TestJDBCTranslator {
JDBCTranslator tr = new AJDBCTranslator(); JDBCTranslator tr = new AJDBCTranslator();
/* Ensure the translation from ADQL to SQL of strings is correct ; /* 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 * particularly, ' should be escaped otherwise it would mean the end of
*(the way to escape a such character is by doubling the character '): */ * a string in SQL (the way to escape a such character is by doubling
* the character '): */
try{ try{
==== BASE ====
assertEquals("'SQL''s translation'", tr.translate(new StringConstant("SQL's translation"))); assertEquals("'SQL''s translation'", tr.translate(new StringConstant("SQL's translation")));
} catch(TranslationException e) { } catch(TranslationException e) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment