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&eacute;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&eacute;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&eacute;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&eacute;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&eacute;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&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();
+	}
+
+}
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&eacute;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&eacute;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&eacute;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);