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