Newer
Older
package adql.query;
/*
* 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 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
* Astronomisches Rechen Institut (ARI)
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
import adql.db.DBColumn;
gmantele
committed
import adql.db.DBType;
import adql.db.DBType.DBDatatype;
import adql.db.DefaultDBColumn;
import adql.parser.ADQLParser;
import adql.parser.ParseException;
import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand;
gmantele
committed
import adql.query.operand.function.DefaultUDF;
import adql.query.operand.function.geometry.BoxFunction;
import adql.query.operand.function.geometry.CentroidFunction;
gmantele
committed
import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.PointFunction;
import adql.query.operand.function.geometry.PolygonFunction;
import adql.query.operand.function.geometry.RegionFunction;
import adql.search.ISearchHandler;
/**
* <p>Object representation of an ADQL query or sub-query.</p>
* <p>The resulting object of the {@link ADQLParser} is an object of this class.</p>
*
* @author Grégory Mantelet (CDS;ARI)
*/
public class ADQLQuery implements ADQLObject {
/** The ADQL clause SELECT. */
private ClauseSelect select;
/** The ADQL clause FROM. */
private FromContent from;
/** The ADQL clause WHERE. */
private ClauseConstraints where;
/** The ADQL clause GROUP BY. */
private ClauseADQL<ColumnReference> groupBy;
/** The ADQL clause HAVING. */
private ClauseConstraints having;
/** The ADQL clause ORDER BY. */
private ClauseADQL<ADQLOrder> orderBy;
/** Position of this Query (or sub-query) inside the whole given ADQL query string.
private TextPosition position = null;
/**
* Builds an empty ADQL query.
*/
public ADQLQuery(){
select = new ClauseSelect();
from = null;
where = new ClauseConstraints("WHERE");
groupBy = new ClauseADQL<ColumnReference>("GROUP BY");
having = new ClauseConstraints("HAVING");
orderBy = new ClauseADQL<ADQLOrder>("ORDER BY");
}
/**
* Builds an ADQL query by copying the given one.
*
* @param toCopy The ADQL query to copy.
* @throws Exception If there is an error during the copy.
*/
@SuppressWarnings("unchecked")
gmantele
committed
public ADQLQuery(ADQLQuery toCopy) throws Exception{
select = (ClauseSelect)toCopy.select.getCopy();
from = (FromContent)toCopy.from.getCopy();
where = (ClauseConstraints)toCopy.where.getCopy();
groupBy = (ClauseADQL<ColumnReference>)toCopy.groupBy.getCopy();
having = (ClauseConstraints)toCopy.having.getCopy();
orderBy = (ClauseADQL<ADQLOrder>)toCopy.orderBy.getCopy();
position = (toCopy.position == null) ? null : new TextPosition(toCopy.position);
}
/**
* Clear all the clauses.
*/
public void reset(){
select.clear();
select.setDistinctColumns(false);
select.setNoLimit();
from = null;
where.clear();
groupBy.clear();
having.clear();
orderBy.clear();
position = null;
}
/**
* Gets the SELECT clause of this query.
*
* @return Its SELECT clause.
*/
gmantele
committed
public final ClauseSelect getSelect(){
return select;
}
/**
* <p>Replaces its SELECT clause by the given one.</p>
*
* <p><i>note: the position of the query is erased.</i></p>
*
* @param newSelect The new SELECT clause.
*
* @throws NullPointerException If the given SELECT clause is <i>null</i>.
*/
gmantele
committed
public void setSelect(ClauseSelect newSelect) throws NullPointerException{
if (newSelect == null)
throw new NullPointerException("Impossible to replace the SELECT clause of a query by NULL !");
else
select = newSelect;
position = null;
}
/**
* Gets the FROM clause of this query.
*
* @return Its FROM clause.
*/
gmantele
committed
public final FromContent getFrom(){
return from;
}
/**
* <p>Replaces its FROM clause by the given one.</p>
*
* <p><i>note: the position of the query is erased.</i></p>
*
* @param newFrom The new FROM clause.
*
* @throws NullPointerException If the given FROM clause is <i>null</i>.
*/
gmantele
committed
public void setFrom(FromContent newFrom) throws NullPointerException{
if (newFrom == null)
throw new NullPointerException("Impossible to replace the FROM clause of a query by NULL !");
else
from = newFrom;
position = null;
}
/**
* Gets the WHERE clause of this query.
*
* @return Its WHERE clause.
*/
gmantele
committed
public final ClauseConstraints getWhere(){
return where;
}
/**
* <p>Replaces its WHERE clause by the given one.</p>
*
* <p><i>note: the position of the query is erased.</i></p>
*
* @param newWhere The new WHERE clause.
*
* @throws NullPointerException If the given WHERE clause is <i>null</i>.
*/
gmantele
committed
public void setWhere(ClauseConstraints newWhere) throws NullPointerException{
if (newWhere == null)
where.clear();
else
where = newWhere;
position = null;
}
/**
* Gets the GROUP BY clause of this query.
*
* @return Its GROUP BY clause.
*/
gmantele
committed
public final ClauseADQL<ColumnReference> getGroupBy(){
return groupBy;
}
/**
* <p>Replaces its GROUP BY clause by the given one.</p>
*
* <p><i>note: the position of the query is erased.</i></p>
*
* @param newGroupBy The new GROUP BY clause.
* @throws NullPointerException If the given GROUP BY clause is <i>null</i>.
*/
gmantele
committed
public void setGroupBy(ClauseADQL<ColumnReference> newGroupBy) throws NullPointerException{
if (newGroupBy == null)
groupBy.clear();
else
groupBy = newGroupBy;
position = null;
}
/**
* Gets the HAVING clause of this query.
*
* @return Its HAVING clause.
*/
gmantele
committed
public final ClauseConstraints getHaving(){
return having;
}
/**
* <p>Replaces its HAVING clause by the given one.</p>
*
* <p><i>note: the position of the query is erased.</i></p>
*
* @param newHaving The new HAVING clause.
* @throws NullPointerException If the given HAVING clause is <i>null</i>.
*/
gmantele
committed
public void setHaving(ClauseConstraints newHaving) throws NullPointerException{
if (newHaving == null)
having.clear();
else
having = newHaving;
position = null;
}
/**
* Gets the ORDER BY clause of this query.
*
* @return Its ORDER BY clause.
*/
gmantele
committed
public final ClauseADQL<ADQLOrder> getOrderBy(){
return orderBy;
}
/**
* <p>Replaces its ORDER BY clause by the given one.</p>
*
* <p><i>note: the position of the query is erased.</i></p>
*
* @param newOrderBy The new ORDER BY clause.
* @throws NullPointerException If the given ORDER BY clause is <i>null</i>.
*/
gmantele
committed
public void setOrderBy(ClauseADQL<ADQLOrder> newOrderBy) throws NullPointerException{
if (newOrderBy == null)
orderBy.clear();
else
orderBy = newOrderBy;
position = null;
}
@Override
public final TextPosition getPosition(){
return position;
}
/**
* Set the position of this {@link ADQLQuery} (or sub-query) inside the whole given ADQL query string.
*
* @param position New position of this {@link ADQLQuery}.
*/
public final void setPosition(final TextPosition position){
this.position = position;
@Override
gmantele
committed
public ADQLObject getCopy() throws Exception{
return new ADQLQuery(this);
}
@Override
gmantele
committed
public String getName(){
return "{ADQL query}";
}
/**
* <p>Gets the list of columns (database metadata) selected by this query.</p>
*
* <p><i><u>Note:</u> The list is generated on the fly !</i></p>
*
* @return Selected columns metadata.
*/
public DBColumn[] getResultingColumns(){
ArrayList<DBColumn> columns = new ArrayList<DBColumn>(select.size());
for(SelectItem item : select){
ADQLOperand operand = item.getOperand();
if (item instanceof SelectAllColumns){
try{
// If "{table}.*", add all columns of the specified table:
if (((SelectAllColumns)item).getAdqlTable() != null)
columns.addAll(((SelectAllColumns)item).getAdqlTable().getDBColumns());
// Otherwise ("*"), add all columns of all selected tables:
else
columns.addAll(from.getDBColumns());
}catch(ParseException pe){
// Here, this error should not occur any more, since it must have been caught by the DBChecker!
}
gmantele
committed
// Create the DBColumn:
DBColumn col = null;
// ...whose the name will be set with the SELECT item's alias:
if (item.hasAlias()){
if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null){
col = ((ADQLColumn)operand).getDBLink();
col = col.copy(col.getDBName(), item.getAlias(), col.getTable());
}else
col = new DefaultDBColumn(item.getAlias(), null);
gmantele
committed
}
// ...or whose the name will be the name of the SELECT item:
else{
if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null)
col = ((ADQLColumn)operand).getDBLink();
col = new DefaultDBColumn(item.getName(), null);
}
gmantele
committed
/* For columns created by default (from functions and operations generally),
gmantele
committed
* set the adequate type if known: */
// CASE: Well-defined UDF
if (operand instanceof DefaultUDF && ((DefaultUDF)operand).getDefinition() != null){
DBType type = ((DefaultUDF)operand).getDefinition().returnType;
((DefaultDBColumn)col).setDatatype(type);
}
// CASE: Point type:
else if (operand instanceof PointFunction || operand instanceof CentroidFunction)
gmantele
committed
((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.POINT));
// CASE: Region type:
else if (operand instanceof RegionFunction || operand instanceof CircleFunction || operand instanceof BoxFunction || operand instanceof PolygonFunction)
((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.REGION));
// CASE: String and numeric types
else if (col instanceof DefaultDBColumn && col.getDatatype() == null && operand.isNumeric() != operand.isString()){
// CASE: String types
if (operand.isString())
((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.VARCHAR));
// CASE: Numeric types:
/* Note: a little special case here since a numeric could be a real, double, integer, or anything
* else and that we don't know precisely here. So we set the special UNKNOWN NUMERIC type. */
else
((DefaultDBColumn)col).setDatatype(new DBType(DBDatatype.UNKNOWN_NUMERIC));
}
// Add the new column to the list:
columns.add(col);
}
}
DBColumn[] resColumns = new DBColumn[columns.size()];
return columns.toArray(resColumns);
}
/**
* Lets searching ADQL objects into this ADQL query thanks to the given search handler.
*
* @param sHandler A search handler.
*
* @return An iterator on all ADQL objects found.
*/
public Iterator<ADQLObject> search(ISearchHandler sHandler){
sHandler.search(this);
return sHandler.iterator();
}
@Override
public ADQLIterator adqlIterator(){
gmantele
committed
return new ADQLIterator(){
private int index = -1;
private ClauseADQL<?> currentClause = null;
@Override
gmantele
committed
public ADQLObject next(){
index++;
switch(index){
gmantele
committed
case 0:
currentClause = select;
break;
case 1:
currentClause = null;
return from;
case 2:
currentClause = where;
break;
case 3:
currentClause = groupBy;
break;
case 4:
currentClause = having;
break;
case 5:
currentClause = orderBy;
break;
default:
throw new NoSuchElementException();
}
return currentClause;
}
@Override
gmantele
committed
public boolean hasNext(){
return index + 1 < 6;
@Override
@SuppressWarnings("unchecked")
gmantele
committed
public void replace(ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException{
if (index <= -1)
throw new IllegalStateException("replace(ADQLObject) impossible: next() has not yet been called !");
if (replacer == null)
remove();
else{
switch(index){
gmantele
committed
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
case 0:
if (replacer instanceof ClauseSelect)
select = (ClauseSelect)replacer;
else
throw new UnsupportedOperationException("Impossible to replace a ClauseSelect (" + select.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
break;
case 1:
if (replacer instanceof FromContent)
from = (FromContent)replacer;
else
throw new UnsupportedOperationException("Impossible to replace a FromContent (" + from.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
break;
case 2:
if (replacer instanceof ClauseConstraints)
where = (ClauseConstraints)replacer;
else
throw new UnsupportedOperationException("Impossible to replace a ClauseConstraints (" + where.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
break;
case 3:
if (replacer instanceof ClauseADQL)
groupBy = (ClauseADQL<ColumnReference>)replacer;
else
throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + groupBy.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
break;
case 4:
if (replacer instanceof ClauseConstraints)
having = (ClauseConstraints)replacer;
else
throw new UnsupportedOperationException("Impossible to replace a ClauseConstraints (" + having.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
break;
case 5:
if (replacer instanceof ClauseADQL)
orderBy = (ClauseADQL<ADQLOrder>)replacer;
else
throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + orderBy.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
break;
position = null;
@Override
gmantele
committed
public void remove(){
if (index <= -1)
throw new IllegalStateException("remove() impossible: next() has not yet been called !");
if (index == 0 || index == 1)
gmantele
committed
throw new UnsupportedOperationException("Impossible to remove a " + ((index == 0) ? "SELECT" : "FROM") + " clause from a query !");
else{
currentClause.clear();
position = null;
}
}
};
}
@Override
gmantele
committed
public String toADQL(){
StringBuffer adql = new StringBuffer(select.toADQL());
adql.append("\nFROM ").append(from.toADQL());
if (!where.isEmpty())
adql.append('\n').append(where.toADQL());
if (!groupBy.isEmpty())
adql.append('\n').append(groupBy.toADQL());
if (!having.isEmpty())
adql.append('\n').append(having.toADQL());
if (!orderBy.isEmpty())
adql.append('\n').append(orderBy.toADQL());
return adql.toString();
}