diff --git a/src/adql/parser/adqlGrammar.jj b/src/adql/parser/adqlGrammar.jj
index f3c6eb47cc0fca334a8db305ca63f2452a757c9b..0532d2f372cfa8cd140844c61a1a22878a96f459 100644
--- a/src/adql/parser/adqlGrammar.jj
+++ b/src/adql/parser/adqlGrammar.jj
@@ -1077,6 +1077,7 @@ TOKEN : {
 	< SELECT: "SELECT" >
 |	< QUANTIFIER: "DISTINCT" | "ALL" >
 |	< TOP: "TOP" >
+|	< OFFSET: "OFFSET" >
 }
 
 /* ************* */
@@ -1300,12 +1301,22 @@ void Select(): {ClauseSelect select = query.getSelect(); SelectItem item=null; T
 	  }
 	 }
 	]
+	[<OFFSET> t=<UNSIGNED_INTEGER>
+	 {
+	  try{
+	  	select.setOffset(Integer.parseInt(t.image));
+	  }catch(NumberFormatException nfe){
+	  	throw new ParseException("[l."+t.beginLine+";c."+t.beginColumn+"] The OFFSET limit (\""+t.image+"\") isn't a regular unsigned integer !");
+	  }
+	 }
+	]
 	
 	item=SelectItem() {select.add(item);}
 	(<COMMA> item=SelectItem() {select.add(item);})*
 	{
 		TextPosition lastItemPos = query.getSelect().get(query.getSelect().size()-1).getPosition();
-		select.setPosition(new TextPosition(start.beginLine, start.beginColumn, lastItemPos.endLine, lastItemPos.endColumn));
	}
+		select.setPosition(new TextPosition(start.beginLine, start.beginColumn, lastItemPos.endLine, lastItemPos.endColumn));
+	}
 }
 
 SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true); IdentifierItem id = null, label = null; ADQLOperand op = null; SelectItem item; Token starToken;} {
@@ -1349,7 +1360,8 @@ SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true
 			if (label != null){
 				item.setCaseSensitive(label.caseSensitivity);
 				item.setPosition(new TextPosition(op.getPosition(), label.position));
-			}else
				item.setPosition(new TextPosition(op.getPosition()));
+			}else
+				item.setPosition(new TextPosition(op.getPosition()));
 			return item;
 		}catch(Exception ex){
 			throw generateParseException(ex);
@@ -1772,7 +1784,8 @@ ADQLOperand StringExpression(): {ADQLOperand leftOp; ADQLOperand rightOp = null;
 	)*
 	{
 		if (leftOp instanceof Concatenation){
-			Concatenation concat = (Concatenation)leftOp;
			concat.setPosition(new TextPosition(concat.get(0).getPosition(), concat.get(concat.size()-1).getPosition()));
+			Concatenation concat = (Concatenation)leftOp;
+			concat.setPosition(new TextPosition(concat.get(0).getPosition(), concat.get(concat.size()-1).getPosition()));
 		}
 	  return leftOp;
 	}
@@ -1788,7 +1801,8 @@ ADQLOperand StringFactor(): {ADQLOperand op;} {
 GeometryValue<GeometryFunction> GeometryExpression(): {ADQLColumn col = null; GeometryFunction gf = null;} {
 	(col=Column() | gf=GeometryValueFunction())
 	{
-		if (col != null){
		  	col.setExpectedType('G');
+		if (col != null){
+		  	col.setExpectedType('G');
 			return new GeometryValue<GeometryFunction>(col);
 		}else
 			return new GeometryValue<GeometryFunction>(gf);
@@ -1804,7 +1818,8 @@ ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint cons
 		constraint=Constraint()
 		{
 			if (notOp){
-			  	TextPosition oldPos = constraint.getPosition();
				constraint = queryFactory.createNot(constraint);
+			  	TextPosition oldPos = constraint.getPosition();
+				constraint = queryFactory.createNot(constraint);
 				((NotConstraint)constraint).setPosition(new TextPosition(op.beginLine, op.beginColumn, oldPos.endLine, oldPos.endColumn));
 			}
 			notOp = false;
@@ -1836,7 +1851,8 @@ ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint cons
 		throw generateParseException(ex);
 	}
 	{
-	  	if (!clause.isEmpty()){
	  		TextPosition start = clause.get(0).getPosition();
+	  	if (!clause.isEmpty()){
+	  		TextPosition start = clause.get(0).getPosition();
 	  		TextPosition end = clause.get(clause.size()-1).getPosition();
 			clause.setPosition(new TextPosition(start, end));
 		}
@@ -2013,7 +2029,8 @@ GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<Geometr
 				{
 					if (p1 != null)
 						gvp1 = new GeometryValue<PointFunction>(p1);
-					else{
						col1.setExpectedType('G');
+					else{
+						col1.setExpectedType('G');
 						gvp1 = new GeometryValue<PointFunction>(col1);
 					}
 				}
@@ -2022,7 +2039,8 @@ GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<Geometr
 				{
 					if (p2 != null)
 						gvp2 = new GeometryValue<PointFunction>(p2);
-					else{
						col2.setExpectedType('G');
+					else{
+						col2.setExpectedType('G');
 						gvp2 = new GeometryValue<PointFunction>(col2);
 					}
 				} 
diff --git a/src/adql/query/ClauseSelect.java b/src/adql/query/ClauseSelect.java
index 26256238a220c4b60a03446d51d5608eec1d9c8e..d2140f6e0be5f801928b5d2e94a02ac932e49496 100644
--- a/src/adql/query/ClauseSelect.java
+++ b/src/adql/query/ClauseSelect.java
@@ -43,6 +43,9 @@ public class ClauseSelect extends ClauseADQL<SelectItem> {
 	/** The maximum number of returned rows. */
 	private int limit = -1;
 
+        /** The number of rows to skip before selection. */
+	private int offset = -1;
+
 	/**
 	 * Builds an empty SELECT clause.
 	 */
@@ -143,6 +146,40 @@ public class ClauseSelect extends ClauseADQL<SelectItem> {
 	public final void setLimit(int limit){
 		this.limit = limit;
 	}
+        
+        /**
+	 * Indicates whether this SELECT clause sets an offset.
+	 * 
+	 * @return	<i>true</i> this clause has an OFFSET flag, <i>false</i> otherwise.
+	 */
+	public final boolean hasOffset(){
+		return offset >= 0;
+	}
+
+	/**
+	 * Gets the offset set by this SELECT clause.
+	 * 
+	 * @return	Number of rows the query must skip before selection (SELECT OFFSET offset).
+	 */
+	public final int getOffset(){
+		return offset;
+	}
+
+	/**
+	 * Sets no offset (classic SELECT).
+	 */
+	public final void setNoOffset(){
+		offset = -1;
+	}
+
+	/**
+	 * Changes the number of rows to skip this clause imposes.
+	 * 
+	 * @param limit	The number of rows to skip before selection (SELECT OFFSET offset).
+	 */
+	public final void setOffset(int newOffset){
+		this.offset = newOffset;
+	}
 
 	/**
 	 * <p>Adds an operand to this SELECT clause.</p>
diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java
index e494eacd6280ae9bbdc875f711807df2b621021d..650ab74d82436863d9ee2ce7fceb41eb9b52ddb3 100644
--- a/src/adql/translator/JDBCTranslator.java
+++ b/src/adql/translator/JDBCTranslator.java
@@ -375,6 +375,9 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 
 		if (query.getSelect().hasLimit())
 			sql.append("\nLimit ").append(query.getSelect().getLimit());
+                
+                if(query.getSelect().hasLimit() && query.getSelect().hasOffset())
+                        sql.append(",").append(query.getSelect().getOffset());
 
 		return sql.toString();
 	}
diff --git a/src/adql/translator/MySQLTranslator.java b/src/adql/translator/MySQLTranslator.java
index 0973de6270ce27651ac39c7b3a82e7ddc9276016..65488dfff42ae5ec835092d3b583345416fa4c3b 100644
--- a/src/adql/translator/MySQLTranslator.java
+++ b/src/adql/translator/MySQLTranslator.java
@@ -4,6 +4,7 @@ import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.STCS.Region;
 import adql.parser.ParseException;
+import adql.query.ADQLQuery;
 import adql.query.IdentifierField;
 import adql.query.operand.ADQLOperand;
 import adql.query.operand.Concatenation;
@@ -329,4 +330,32 @@ public class MySQLTranslator extends JDBCTranslator {
 		return getDefaultADQLFunction(region);
 	}
 
+        @Override
+	public String translate(ADQLQuery query) throws TranslationException{
+		StringBuilder sql = new StringBuilder(translate(query.getSelect()));
+
+		sql.append("\nFROM ").append(translate(query.getFrom()));
+
+		if (!query.getWhere().isEmpty())
+			sql.append('\n').append(translate(query.getWhere()));
+
+		if (!query.getGroupBy().isEmpty())
+			sql.append('\n').append(translate(query.getGroupBy()));
+
+		if (!query.getHaving().isEmpty())
+			sql.append('\n').append(translate(query.getHaving()));
+
+		if (!query.getOrderBy().isEmpty())
+			sql.append('\n').append(translate(query.getOrderBy()));
+
+		if (query.getSelect().hasLimit()) {
+                        sql.append("\n LIMIT ");
+                    if(query.getSelect().hasOffset()) {
+                        sql.append(query.getSelect().getOffset()).append(",");
+                    }
+                    sql.append(query.getSelect().getLimit());
+                }
+
+		return sql.toString();
+	}
 }
diff --git a/src/adql/translator/PostgreSQLTranslator.java b/src/adql/translator/PostgreSQLTranslator.java
index 68d740af5e41e4aef09899736af6ec3a92d051a9..6f68ed0897189d0b79f0f3ff00c8ca53d64497a0 100644
--- a/src/adql/translator/PostgreSQLTranslator.java
+++ b/src/adql/translator/PostgreSQLTranslator.java
@@ -24,6 +24,7 @@ import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.STCS.Region;
 import adql.parser.ParseException;
+import adql.query.ADQLQuery;
 import adql.query.IdentifierField;
 import adql.query.operand.StringConstant;
 import adql.query.operand.function.ADQLFunction;
@@ -329,4 +330,30 @@ public class PostgreSQLTranslator extends JDBCTranslator {
 		throw new ParseException("Geometries can not be uploaded in the database in this implementation!");
 	}
 
+        @Override
+	public String translate(ADQLQuery query) throws TranslationException{
+		StringBuffer sql = new StringBuffer(translate(query.getSelect()));
+
+		sql.append("\nFROM ").append(translate(query.getFrom()));
+
+		if (!query.getWhere().isEmpty())
+			sql.append('\n').append(translate(query.getWhere()));
+
+		if (!query.getGroupBy().isEmpty())
+			sql.append('\n').append(translate(query.getGroupBy()));
+
+		if (!query.getHaving().isEmpty())
+			sql.append('\n').append(translate(query.getHaving()));
+
+		if (!query.getOrderBy().isEmpty())
+			sql.append('\n').append(translate(query.getOrderBy()));
+
+		if (query.getSelect().hasLimit())
+			sql.append("\nLimit ").append(query.getSelect().getLimit());
+                
+                if(query.getSelect().hasLimit() && query.getSelect().hasOffset())
+                        sql.append(" offset ").append(query.getSelect().getOffset());
+
+		return sql.toString();
+	}
 }