package adql.translator;
/*
* 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 2015-2016 - Astronomisches Rechen Institut (ARI)
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import adql.db.DBColumn;
import adql.db.DBTable;
import adql.db.DBType;
import adql.db.STCS.Region;
import adql.db.exception.UnresolvedJoinException;
import adql.parser.ParseException;
import adql.query.ADQLList;
import adql.query.ADQLObject;
import adql.query.ADQLOrder;
import adql.query.ADQLQuery;
import adql.query.ClauseConstraints;
import adql.query.ClauseSelect;
import adql.query.ColumnReference;
import adql.query.IdentifierField;
import adql.query.SelectAllColumns;
import adql.query.SelectItem;
import adql.query.constraint.ADQLConstraint;
import adql.query.constraint.Between;
import adql.query.constraint.Comparison;
import adql.query.constraint.ConstraintsGroup;
import adql.query.constraint.Exists;
import adql.query.constraint.In;
import adql.query.constraint.IsNull;
import adql.query.constraint.NotConstraint;
import adql.query.from.ADQLJoin;
import adql.query.from.ADQLTable;
import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand;
import adql.query.operand.Concatenation;
import adql.query.operand.NegativeOperand;
import adql.query.operand.NumericConstant;
import adql.query.operand.Operation;
import adql.query.operand.StringConstant;
import adql.query.operand.WrappedOperand;
import adql.query.operand.function.ADQLFunction;
import adql.query.operand.function.MathFunction;
import adql.query.operand.function.SQLFunction;
import adql.query.operand.function.SQLFunctionType;
import adql.query.operand.function.UserDefinedFunction;
import adql.query.operand.function.geometry.AreaFunction;
import adql.query.operand.function.geometry.BoxFunction;
import adql.query.operand.function.geometry.CentroidFunction;
import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.ContainsFunction;
import adql.query.operand.function.geometry.DistanceFunction;
import adql.query.operand.function.geometry.ExtractCoord;
import adql.query.operand.function.geometry.ExtractCoordSys;
import adql.query.operand.function.geometry.GeometryFunction;
import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
import adql.query.operand.function.geometry.IntersectsFunction;
import adql.query.operand.function.geometry.PointFunction;
import adql.query.operand.function.geometry.PolygonFunction;
import adql.query.operand.function.geometry.RegionFunction;
/**
* <p>Implementation of {@link ADQLTranslator} which translates ADQL queries in SQL queries.</p>
*
* <p>
* It is already able to translate all SQL standard features, but lets abstract the translation of all
* geometrical functions. So, this translator must be extended as {@link PostgreSQLTranslator} and
* {@link PgSphereTranslator} are doing.
* </p>
*
* <p><i>Note:
* Its default implementation of the SQL syntax has been inspired by the PostgreSQL one.
* However, it should work also with SQLite and MySQL, but some translations might be needed
* (as it is has been done for PostgreSQL about the mathematical functions).
* </i></p>
*
* <h3>PostgreSQLTranslator and PgSphereTranslator</h3>
*
* <p>
* {@link PgSphereTranslator} extends {@link PostgreSQLTranslator} and is able to translate geometrical
* functions according to the syntax given by PgSphere. But it can also convert geometrical types
* (from and toward the database), translate PgSphere regions into STC expression and vice-versa.
* </p>
*
* <p>
* {@link PostgreSQLTranslator} overwrites the translation of mathematical functions whose some have
* a different name or signature. Besides, it is also implementing the translation of the geometrical
* functions. However, it does not really translate them. It is just returning the ADQL expression
* (by calling {@link #getDefaultADQLFunction(ADQLFunction)}).
* And so, of course, the execution of a SQL query containing geometrical functions and translated
* using this translator will not work. It is just a default implementation in case there is no interest
* of these geometrical functions.
* </p>
*
* <h3>SQL with or without case sensitivity?</h3>
*
* <p>
* In ADQL and in SQL, it is possible to tell the parser to respect the exact case or not of an identifier (schema, table or column name)
* by surrounding it with double quotes. However ADQL identifiers and SQL ones may be different. In that way, the case sensitivity specified
* in ADQL on the different identifiers can not be kept in SQL. That's why this translator lets specify a general rule on which types of
* SQL identifier must be double quoted. This can be done by implementing the abstract function {@link #isCaseSensitive(IdentifierField)}.
* The functions translating column and table names will call this function in order to surround the identifiers by double quotes or not.
* So, <b>be careful if you want to override the functions translating columns and tables!</b>
* </p>
*
* <h3>Translation of "SELECT TOP"</h3>
*
* <p>
* The default behavior of this translator is to translate the ADQL "TOP" into the SQL "LIMIT" at the end of the query.
* This is ok for some DBMS, but not all. So, if your DBMS does not know the "LIMIT" keyword, you should override the function
* translating the whole query: {@link #translate(ADQLQuery)}. Here is its current implementation:
* </p>
* <pre>
* 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());
* return sql.toString();
* </pre>
*
* <h3>Translation of ADQL functions</h3>
*
* <p>
* All ADQL functions are by default not translated. Consequently, the SQL translation is
* actually the ADQL expression. Generally the ADQL expression is generic enough. However some mathematical functions may need
* to be translated differently. For instance {@link PostgreSQLTranslator} is translating differently: LOG, LOG10, RAND and TRUNC.
* </p>
*
* <p><i>Note:
* Geometrical regions and types have not been managed here. They stay abstract because it is obviously impossible to have a generic
* translation and conversion ; it totally depends from the database system.
* </i></p>
*
* <h3>Translation of "FROM" with JOINs</h3>
*
* <p>
* The FROM clause is translated into SQL as written in ADQL. There is no differences except the identifiers that are replaced.
* The tables' aliases and their case sensitivity are kept like in ADQL.
* </p>
*
* @author Grégory Mantelet (ARI)
* @version 1.4 (07/2016)
* @since 1.4
*
* @see PostgreSQLTranslator
* @see PgSphereTranslator
*/
public abstract class JDBCTranslator implements ADQLTranslator {
/**
* <p>Tell whether the specified identifier MUST be translated so that being interpreted case sensitively or not.
* By default, an identifier that must be translated with case sensitivity will be surrounded by double quotes.
* But, if this function returns FALSE, the SQL name will be written just as given in the metadata, without double quotes.</p>
*
* <p><b>WARNING</b>:
* An {@link IdentifierField} object can be a SCHEMA, TABLE, COLUMN and ALIAS. However, in this translator,
* aliases are translated like in ADQL (so, with the same case sensitivity specification as in ADQL).
* So, this function will never be used to know the case sensitivity to apply to an alias. It is then
* useless to write a special behavior for the ALIAS value.
* </p>
*
* @param field The identifier whose the case sensitive to apply is asked.
*
* @return <i>true</i> if the specified identifier must be translated case sensitivity, <i>false</i> otherwise (included if ALIAS or NULL).
*/
public abstract boolean isCaseSensitive(final IdentifierField field);
/**
* <p>Get the qualified DB name of the schema containing the given table.</p>
*
* <p><i>Note:
* This function will, by default, add double quotes if the schema name must be case sensitive in the SQL query.
* This information is provided by {@link #isCaseSensitive(IdentifierField)}.
* </i></p>
*
* @param table A table of the schema whose the qualified DB name is asked.
*
* @return The qualified (with DB catalog name prefix if any, and with double quotes if needed) DB schema name,
* or an empty string if there is no schema or no DB name.
*/
public String getQualifiedSchemaName(final DBTable table){
if (table == null || table.getDBSchemaName() == null)
return "";
StringBuffer buf = new StringBuffer();
if (table.getDBCatalogName() != null)
appendIdentifier(buf, table.getDBCatalogName(), IdentifierField.CATALOG).append('.');
appendIdentifier(buf, table.getDBSchemaName(), IdentifierField.SCHEMA);
return buf.toString();
}
/**
* <p>Get the qualified DB name of the given table.</p>
*
* <p><i>Note:
* This function will, by default, add double quotes if the table name must be case sensitive in the SQL query.
* This information is provided by {@link #isCaseSensitive(IdentifierField)}.
* </i></p>
*
* @param table The table whose the qualified DB name is asked.
*
* @return The qualified (with DB catalog and schema prefix if any, and with double quotes if needed) DB table name,
* or an empty string if the given table is NULL or if there is no DB name.
*
* @see #getTableName(DBTable, boolean)
*/
public String getQualifiedTableName(final DBTable table){
return getTableName(table, true);
}
/**
* <p>Get the DB name of the given table.
* The second parameter lets specify whether the table name must be prefixed by the qualified schema name or not.</p>
*
* <p><i>Note:
* This function will, by default, add double quotes if the table name must be case sensitive in the SQL query.
* This information is provided by {@link #isCaseSensitive(IdentifierField)}.
* </i></p>
*
* @param table The table whose the DB name is asked.
* @param withSchema <i>true</i> if the qualified schema name must prefix the table name, <i>false</i> otherwise.
*
* @return The DB table name (prefixed by the qualified schema name if asked, and with double quotes if needed),
* or an empty string if the given table is NULL or if there is no DB name.
*
* @since 2.0
*/
public String getTableName(final DBTable table, final boolean withSchema){
if (table == null)
return "";
StringBuffer buf = new StringBuffer();
if (withSchema){
buf.append(getQualifiedSchemaName(table));
if (buf.length() > 0)
buf.append('.');
}
appendIdentifier(buf, table.getDBName(), IdentifierField.TABLE);
return buf.toString();
}
/**
* <p>Get the DB name of the given column</p>
*
* <p><i>Note:
* This function will, by default, add double quotes if the column name must be case sensitive in the SQL query.
* This information is provided by {@link #isCaseSensitive(IdentifierField)}.
* </i></p>
*
* <p><b>Caution:
* The given column may be NULL and in this case an empty string will be returned.
* But if the given column is not NULL, its DB name MUST NOT BE NULL!
* </b></p>
*
* @param column The column whose the DB name is asked.
*
* @return The DB column name (with double quotes if needed),
* or an empty string if the given column is NULL.
*/
public String getColumnName(final DBColumn column){
return (column == null) ? "" : appendIdentifier(new StringBuffer(), column.getDBName(), IdentifierField.COLUMN).toString();
}
/**
* <p>Appends the given identifier in the given StringBuffer.</p>
*
* <p>
* This function just call {@link #appendIdentifier(StringBuffer, String, boolean)}
* with the same 2 first parameters. The third one is the result of:
* <code>{@link #isCaseSensitive(IdentifierField) isCaseSensitive(field)}</code>.
* </p>
*
* <p><i>Note:
* In order to keep a consistent output of the <code>appendIdentifier(...)</code> functions,
* this function can not be overwritten ; it is just a shortcut function.
* </i></p>
*
* @param str The string buffer.
* @param id The identifier to append.
* @param field The type of identifier (column, table, schema, catalog or alias ?).
*
* @return The string buffer + identifier.
*
* @see #appendIdentifier(StringBuffer, String, boolean)
*/
public final StringBuffer appendIdentifier(final StringBuffer str, final String id, final IdentifierField field){
return appendIdentifier(str, id, isCaseSensitive(field));
}
/**
* Appends the given identifier to the given StringBuffer.
*
* @param str The string buffer.
* @param id The identifier to append.
* @param caseSensitive <i>true</i> to format the identifier so that preserving the case sensitivity, <i>false</i> otherwise.
*
* @return The string buffer + identifier.
*/
public StringBuffer appendIdentifier(final StringBuffer str, final String id, final boolean caseSensitive){
if (caseSensitive && !id.matches("\"[^\"]*\""))
return str.append('"').append(id).append('"');
else
return str.append(id);
}
@Override
@SuppressWarnings({"unchecked","rawtypes"})
public String translate(ADQLObject obj) throws TranslationException{
if (obj instanceof ADQLQuery)
return translate((ADQLQuery)obj);
else if (obj instanceof ADQLList)
return translate((ADQLList)obj);
else if (obj instanceof SelectItem)
return translate((SelectItem)obj);
else if (obj instanceof ColumnReference)
return translate((ColumnReference)obj);
else if (obj instanceof ADQLTable)
return translate((ADQLTable)obj);
else if (obj instanceof ADQLJoin)
return translate((ADQLJoin)obj);
else if (obj instanceof ADQLOperand)
return translate((ADQLOperand)obj);
else if (obj instanceof ADQLConstraint)
return translate((ADQLConstraint)obj);
else
return obj.toADQL();
}
@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());
return sql.toString();
}
/* *************************** */
/* ****** LIST & CLAUSE ****** */
/* *************************** */
@Override
public String translate(ADQLList<? extends ADQLObject> list) throws TranslationException{
if (list instanceof ClauseSelect)
return translate((ClauseSelect)list);
else if (list instanceof ClauseConstraints)
return translate((ClauseConstraints)list);
else
return getDefaultADQLList(list);
}
/**
* Gets the default SQL output for a list of ADQL objects.
*
* @param list List to format into SQL.
*
* @return The corresponding SQL.
*
* @throws TranslationException If there is an error during the translation.
*/
protected String getDefaultADQLList(ADQLList<? extends ADQLObject> list) throws TranslationException{
String sql = (list.getName() == null) ? "" : (list.getName() + " ");
for(int i = 0; i < list.size(); i++)
sql += ((i == 0) ? "" : (" " + list.getSeparator(i) + " ")) + translate(list.get(i));
return sql;
}
@Override
public String translate(ClauseSelect clause) throws TranslationException{
String sql = null;
for(int i = 0; i < clause.size(); i++){
if (i == 0){
sql = clause.getName() + (clause.distinctColumns() ? " DISTINCT" : "");
}else
sql += " " + clause.getSeparator(i);
sql += " " + translate(clause.get(i));
}
return sql;
}
@Override
public String translate(ClauseConstraints clause) throws TranslationException{
if (clause instanceof ConstraintsGroup)
return "(" + getDefaultADQLList(clause) + ")";
else
return getDefaultADQLList(clause);
}
@Override
public String translate(SelectItem item) throws TranslationException{
if (item instanceof SelectAllColumns)
return translate((SelectAllColumns)item);
StringBuffer translation = new StringBuffer(translate(item.getOperand()));
if (item.hasAlias()){
translation.append(" AS ");
appendIdentifier(translation, item.getAlias(), item.isCaseSensitive());
}else{
translation.append(" AS ");
appendIdentifier(translation, item.getName(), true);
}
return translation.toString();
}
@Override
public String translate(SelectAllColumns item) throws TranslationException{
HashMap<String,String> mapAlias = new HashMap<String,String>();
// Fetch the full list of columns to display:
Iterable<DBColumn> dbCols = null;
if (item.getAdqlTable() != null && item.getAdqlTable().getDBLink() != null){
ADQLTable table = item.getAdqlTable();
dbCols = table.getDBLink();
if (table.hasAlias()){
String key = getQualifiedTableName(table.getDBLink());
mapAlias.put(key, table.isCaseSensitive(IdentifierField.ALIAS) ? ("\"" + table.getAlias() + "\"") : table.getAlias());
}
}else if (item.getQuery() != null){
try{
dbCols = item.getQuery().getFrom().getDBColumns();
}catch(UnresolvedJoinException pe){
throw new TranslationException("Due to a join problem, the ADQL to SQL translation can not be completed!", pe);
}
ArrayList<ADQLTable> tables = item.getQuery().getFrom().getTables();
for(ADQLTable table : tables){
if (table.hasAlias()){
String key = getQualifiedTableName(table.getDBLink());
mapAlias.put(key, table.isCaseSensitive(IdentifierField.ALIAS) ? ("\"" + table.getAlias() + "\"") : table.getAlias());
}
}
}
// Write the DB name of all these columns:
if (dbCols != null){
StringBuffer cols = new StringBuffer();
for(DBColumn col : dbCols){
if (cols.length() > 0)
cols.append(',');
if (col.getTable() != null){
String fullDbName = getQualifiedTableName(col.getTable());
if (mapAlias.containsKey(fullDbName))
appendIdentifier(cols, mapAlias.get(fullDbName), false).append('.');
else
cols.append(fullDbName).append('.');
}
appendIdentifier(cols, col.getDBName(), IdentifierField.COLUMN);
cols.append(" AS \"").append(col.getADQLName()).append('\"');
}
return (cols.length() > 0) ? cols.toString() : item.toADQL();
}else{
return item.toADQL();
}
}
@Override
public String translate(ColumnReference ref) throws TranslationException{
if (ref instanceof ADQLOrder)
return translate((ADQLOrder)ref);
else
return getDefaultColumnReference(ref);
}
/**
* Gets the default SQL output for a column reference.
*
* @param ref The column reference to format into SQL.
*
* @return The corresponding SQL.
*
* @throws TranslationException If there is an error during the translation.
*/
protected String getDefaultColumnReference(ColumnReference ref) throws TranslationException{
if (ref.isIndex()){
return "" + ref.getColumnIndex();
}else{
if (ref.getDBLink() == null){
return (ref.isCaseSensitive() ? ("\"" + ref.getColumnName() + "\"") : ref.getColumnName());
}else{
DBColumn dbCol = ref.getDBLink();
StringBuffer colName = new StringBuffer();
// Use the table alias if any:
if (ref.getAdqlTable() != null && ref.getAdqlTable().hasAlias())
appendIdentifier(colName, ref.getAdqlTable().getAlias(), ref.getAdqlTable().isCaseSensitive(IdentifierField.ALIAS)).append('.');
// Use the DBTable if any:
else if (dbCol.getTable() != null)
colName.append(getQualifiedTableName(dbCol.getTable())).append('.');
appendIdentifier(colName, dbCol.getDBName(), IdentifierField.COLUMN);
return colName.toString();
}
}
}
@Override
public String translate(ADQLOrder order) throws TranslationException{
return getDefaultColumnReference(order) + (order.isDescSorting() ? " DESC" : " ASC");
}
/* ************************** */
/* ****** TABLE & JOIN ****** */
/* ************************** */
@Override
public String translate(FromContent content) throws TranslationException{
if (content instanceof ADQLTable)
return translate((ADQLTable)content);
else if (content instanceof ADQLJoin)
return translate((ADQLJoin)content);
else
return content.toADQL();
}
@Override
public String translate(ADQLTable table) throws TranslationException{
StringBuffer sql = new StringBuffer();
// CASE: SUB-QUERY:
if (table.isSubQuery())
sql.append('(').append(translate(table.getSubQuery())).append(')');
// CASE: TABLE REFERENCE:
else{
// Use the corresponding DB table, if known:
if (table.getDBLink() != null)
sql.append(getQualifiedTableName(table.getDBLink()));
// Otherwise, use the whole table name given in the ADQL query:
else
sql.append(table.getFullTableName());
}
// Add the table alias, if any:
if (table.hasAlias()){
sql.append(" AS ");
appendIdentifier(sql, table.getAlias(), table.isCaseSensitive(IdentifierField.ALIAS));
}
return sql.toString();
}
@Override
public String translate(ADQLJoin join) throws TranslationException{
StringBuffer sql = new StringBuffer(translate(join.getLeftTable()));
if (join.isNatural())
sql.append(" NATURAL");
sql.append(' ').append(join.getJoinType()).append(' ').append(translate(join.getRightTable())).append(' ');
if (!join.isNatural()){
if (join.getJoinCondition() != null)
sql.append(translate(join.getJoinCondition()));
else if (join.hasJoinedColumns()){
StringBuffer cols = new StringBuffer();
Iterator<ADQLColumn> it = join.getJoinedColumns();
while(it.hasNext()){
ADQLColumn item = it.next();
if (cols.length() > 0)
cols.append(", ");
if (item.getDBLink() == null)
appendIdentifier(cols, item.getColumnName(), item.isCaseSensitive(IdentifierField.COLUMN));
else
appendIdentifier(cols, item.getDBLink().getDBName(), IdentifierField.COLUMN);
}
sql.append("USING (").append(cols).append(')');
}
}
return sql.toString();
}
/* ********************* */
/* ****** OPERAND ****** */
/* ********************* */
@Override
public String translate(ADQLOperand op) throws TranslationException{
if (op instanceof ADQLColumn)
return translate((ADQLColumn)op);
else if (op instanceof Concatenation)
return translate((Concatenation)op);
else if (op instanceof NegativeOperand)
return translate((NegativeOperand)op);
else if (op instanceof NumericConstant)
return translate((NumericConstant)op);
else if (op instanceof StringConstant)
return translate((StringConstant)op);
else if (op instanceof WrappedOperand)
return translate((WrappedOperand)op);
else if (op instanceof Operation)
return translate((Operation)op);
else if (op instanceof ADQLFunction)
return translate((ADQLFunction)op);
else
return op.toADQL();
}
@Override
public String translate(ADQLColumn column) throws TranslationException{
// Use its DB name if known:
if (column.getDBLink() != null){
DBColumn dbCol = column.getDBLink();
StringBuffer colName = new StringBuffer();
// Use the table alias if any:
if (column.getAdqlTable() != null && column.getAdqlTable().hasAlias())
appendIdentifier(colName, column.getAdqlTable().getAlias(), column.getAdqlTable().isCaseSensitive(IdentifierField.ALIAS)).append('.');
// Use the DBTable if any:
else if (dbCol.getTable() != null && dbCol.getTable().getDBName() != null)
colName.append(getQualifiedTableName(dbCol.getTable())).append('.');
// Otherwise, use the prefix of the column given in the ADQL query:
else if (column.getTableName() != null)
colName = column.getFullColumnPrefix().append('.');
appendIdentifier(colName, dbCol.getDBName(), IdentifierField.COLUMN);
return colName.toString();
}
// Otherwise, use the whole name given in the ADQL query:
else
return column.getFullColumnName();
}
@Override
public String translate(Concatenation concat) throws TranslationException{
return translate((ADQLList<ADQLOperand>)concat);
}
@Override
public String translate(NegativeOperand negOp) throws TranslationException{
return "-" + translate(negOp.getOperand());
}
@Override
public String translate(NumericConstant numConst) throws TranslationException{
return numConst.getValue();
}
@Override
public String translate(StringConstant strConst) throws TranslationException{
return "'" + strConst.getValue().replaceAll("'", "''") + "'";
}
@Override
public String translate(WrappedOperand op) throws TranslationException{
return "(" + translate(op.getOperand()) + ")";
}
@Override
public String translate(Operation op) throws TranslationException{
return translate(op.getLeftOperand()) + op.getOperation().toADQL() + translate(op.getRightOperand());
}
/* ************************ */
/* ****** CONSTRAINT ****** */
/* ************************ */
@Override
public String translate(ADQLConstraint cons) throws TranslationException{
if (cons instanceof Comparison)
return translate((Comparison)cons);
else if (cons instanceof Between)
return translate((Between)cons);
else if (cons instanceof Exists)
return translate((Exists)cons);
else if (cons instanceof In)
return translate((In)cons);
else if (cons instanceof IsNull)
return translate((IsNull)cons);
else if (cons instanceof NotConstraint)
return translate((NotConstraint)cons);
else
return cons.toADQL();
}
@Override
public String translate(Comparison comp) throws TranslationException{
return translate(comp.getLeftOperand()) + " " + comp.getOperator().toADQL() + " " + translate(comp.getRightOperand());
}
@Override
public String translate(Between comp) throws TranslationException{
return translate(comp.getLeftOperand()) + " " + comp.getName() + " " + translate(comp.getMinOperand()) + " AND " + translate(comp.getMaxOperand());
}
@Override
public String translate(Exists exists) throws TranslationException{
return "EXISTS(" + translate(exists.getSubQuery()) + ")";
}
@Override
public String translate(In in) throws TranslationException{
return translate(in.getOperand()) + " " + in.getName() + " (" + (in.hasSubQuery() ? translate(in.getSubQuery()) : translate(in.getValuesList())) + ")";
}
@Override
public String translate(IsNull isNull) throws TranslationException{
return translate(isNull.getColumn()) + " " + isNull.getName();
}
@Override
public String translate(NotConstraint notCons) throws TranslationException{
return "NOT " + translate(notCons.getConstraint());
}
/* *********************** */
/* ****** FUNCTIONS ****** */
/* *********************** */
@Override
public String translate(ADQLFunction fct) throws TranslationException{
if (fct instanceof GeometryFunction)
return translate((GeometryFunction)fct);
else if (fct instanceof MathFunction)
return translate((MathFunction)fct);
else if (fct instanceof SQLFunction)
return translate((SQLFunction)fct);
else if (fct instanceof UserDefinedFunction)
return translate((UserDefinedFunction)fct);
else
return getDefaultADQLFunction(fct);
}
/**
* Gets the default SQL output for the given ADQL function.
*
* @param fct The ADQL function to format into SQL.
*
* @return The corresponding SQL.
*
* @throws TranslationException If there is an error during the translation.
*/
protected final String getDefaultADQLFunction(ADQLFunction fct) throws TranslationException{
String sql = fct.getName() + "(";
for(int i = 0; i < fct.getNbParameters(); i++)
sql += ((i == 0) ? "" : ", ") + translate(fct.getParameter(i));
return sql + ")";
}
@Override
public String translate(SQLFunction fct) throws TranslationException{
if (fct.getType() == SQLFunctionType.COUNT_ALL)
return "COUNT(" + (fct.isDistinct() ? "DISTINCT " : "") + "*)";
else
return fct.getName() + "(" + (fct.isDistinct() ? "DISTINCT " : "") + translate(fct.getParameter(0)) + ")";
}
@Override
public String translate(MathFunction fct) throws TranslationException{
return getDefaultADQLFunction(fct);
}
@Override
public String translate(UserDefinedFunction fct) throws TranslationException{
return fct.translate(this);
}
/* *********************************** */
/* ****** GEOMETRICAL FUNCTIONS ****** */
/* *********************************** */
@Override
public String translate(GeometryFunction fct) throws TranslationException{
if (fct instanceof AreaFunction)
return translate((AreaFunction)fct);
else if (fct instanceof BoxFunction)
return translate((BoxFunction)fct);
else if (fct instanceof CentroidFunction)
return translate((CentroidFunction)fct);
else if (fct instanceof CircleFunction)
return translate((CircleFunction)fct);
else if (fct instanceof ContainsFunction)
return translate((ContainsFunction)fct);
else if (fct instanceof DistanceFunction)
return translate((DistanceFunction)fct);
else if (fct instanceof ExtractCoord)
return translate((ExtractCoord)fct);
else if (fct instanceof ExtractCoordSys)
return translate((ExtractCoordSys)fct);
else if (fct instanceof IntersectsFunction)
return translate((IntersectsFunction)fct);
else if (fct instanceof PointFunction)
return translate((PointFunction)fct);
else if (fct instanceof PolygonFunction)
return translate((PolygonFunction)fct);
else if (fct instanceof RegionFunction)
return translate((RegionFunction)fct);
else
return getDefaultADQLFunction(fct);
}
@Override
public String translate(GeometryValue<? extends GeometryFunction> geomValue) throws TranslationException{
return translate(geomValue.getValue());
}
/**
* Convert any type provided by a JDBC driver into a type understandable by the ADQL/TAP library.
*
* @param dbmsType Type returned by a JDBC driver. <i>Note: this value is returned by ResultSetMetadata.getColumnType(int) and correspond to a type of java.sql.Types</i>
* @param rawDbmsTypeName Full name of the type returned by a JDBC driver. <i>Note: this name is returned by ResultSetMetadata.getColumnTypeName(int) ; this name may contain parameters</i>
* @param dbmsTypeName Name of type, without the eventual parameters. <i>Note: this name is extracted from rawDbmsTypeName.</i>
* @param typeParams The eventual type parameters (e.g. char string length). <i>Note: these parameters are extracted from rawDbmsTypeName.</i>
*
* @return The corresponding ADQL/TAP type or NULL if the specified type is unknown.
*/
public abstract DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, final String dbmsTypeName, final String[] typeParams);
/**
* <p>Convert any type provided by the ADQL/TAP library into a type understandable by a JDBC driver.</p>
*
* <p><i>Note:
* The returned DBMS type may contain some parameters between brackets.
* </i></p>
*
* @param type The ADQL/TAP library's type to convert.
*
* @return The corresponding DBMS type or NULL if the specified type is unknown.
*/
public abstract String convertTypeToDB(final DBType type);
/**
* <p>Parse the given JDBC column value as a geometry object and convert it into a {@link Region}.</p>
*
* <p><i>Note:
* Generally the returned object will be used to get its STC-S expression.
* </i></p>
*
* <p><i>Note:
* If the given column value is NULL, NULL will be returned.
* </i></p>
*
* <p><i><b>Important note:</b>
* This function is called ONLY for value of columns flagged as geometries by
* {@link #convertTypeFromDB(int, String, String, String[])}. So the value should always
* be of the expected type and format. However, if it turns out that the type is wrong
* and that the conversion is finally impossible, this function SHOULD throw a
* {@link tap.data.DataReadException}.
* </i></p>
*
* @param jdbcColValue A JDBC column value (returned by ResultSet.getObject(int)).
*
* @return The corresponding {@link Region} if the given value is a geometry.
*
* @throws ParseException If the given object is not a geometrical object
* or can not be transformed into a {@link Region} object.
*/
public abstract Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException;
/**
* <p>Convert the given STC region into a DB column value.</p>
*
* <p><i>Note:
* This function is used only by the UPLOAD feature, to import geometries provided as STC-S expression in
* a VOTable document inside a DB column.
* </i></p>
*
* <p><i>Note:
* If the given region is NULL, NULL will be returned.
* </i></p>
*
* @param region The region to store in the DB.
*
* @return The corresponding DB column object.
*
* @throws ParseException If the given STC Region can not be converted into a DB object.
*/
public abstract Object translateGeometryToDB(final Region region) throws ParseException;
}