Skip to content
Snippets Groups Projects
Commit e03e5725 authored by gmantele's avatar gmantele
Browse files

[ADQL] Fix the transformation of NATURAL JOIN and JOIN...USING of MS-SQLServer.

In the resulting SQL query, if there are an alias on the joined tables, these
aliases must be used in the ON clause (instead of the full table name).

For instance, the following ADQL query:

```sql
  SELECT *
  FROM tableA AS a NATURAL JOIN tableB AS b;
```

should be translated into the following SQL:

```sql
  SELECT *
  FROM tableA AS a
    INNER JOIN tableB AS b
      ON a.id = b.id
```

This commit complete the resolution of the Pull Request #16
(more details about the issue can be got in there)
parent 73630130
No related branches found
No related tags found
No related merge requests found
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 2016-2017 - Astronomisches Rechen Institut (ARI)
*/
import java.util.ArrayList;
import java.util.Iterator;
import adql.db.DBChecker;
import adql.db.DBColumn;
import adql.db.DBTable;
import adql.db.DBType;
import adql.db.DBType.DBDatatype;
import adql.db.DefaultDBColumn;
import adql.db.DefaultDBTable;
import adql.db.STCS.Region;
import adql.db.SearchColumnList;
import adql.db.exception.UnresolvedJoinException;
import adql.parser.ADQLParser;
import adql.parser.ParseException;
import adql.parser.SQLServer_ADQLQueryFactory;
import adql.query.ADQLQuery;
import adql.query.ClauseSelect;
import adql.query.IdentifierField;
import adql.query.from.ADQLJoin;
import adql.query.from.ADQLTable;
import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn;
import adql.query.operand.function.MathFunction;
import adql.query.operand.function.geometry.AreaFunction;
......@@ -83,30 +60,6 @@ import adql.query.operand.function.geometry.RegionFunction;
*/
public class SQLServerTranslator extends JDBCTranslator {
/* TODO Temporary MAIN function.
* TO REMOVE for the release. */
public final static void main(final String[] args) throws Exception{
final String adqlquery = "SELECT id, name, aColumn, anotherColumn FROM aTable A NATURAL JOIN anotherTable B;";
System.out.println("ADQL Query:\n" + adqlquery);
ArrayList<DBTable> tables = new ArrayList<DBTable>(2);
DefaultDBTable t = new DefaultDBTable("aTable");
t.addColumn(new DefaultDBColumn("id", t));
t.addColumn(new DefaultDBColumn("name", t));
t.addColumn(new DefaultDBColumn("aColumn", t));
tables.add(t);
t = new DefaultDBTable("anotherTable");
t.addColumn(new DefaultDBColumn("id", t));
t.addColumn(new DefaultDBColumn("name", t));
t.addColumn(new DefaultDBColumn("anotherColumn", t));
tables.add(t);
ADQLQuery query = (new ADQLParser(new DBChecker(tables), new SQLServer_ADQLQueryFactory())).parseQuery(adqlquery);
SQLServerTranslator translator = new SQLServerTranslator();
System.out.println("\nIn MS SQL Server:\n" + translator.translate(query));
}
/** <p>Indicate the case sensitivity to apply to each SQL identifier (only SCHEMA, TABLE and COLUMN).</p>
*
* <p><i>Note:
......@@ -225,9 +178,9 @@ public class SQLServerTranslator extends JDBCTranslator {
// ...append the corresponding join condition:
if (buf.length() > 0)
buf.append(" AND ");
buf.append(getQualifiedTableName(leftCol.getTable())).append('.').append(getColumnName(leftCol));
buf.append(translate(generateJoinColumn(join.getLeftTable(), leftCol, new ADQLColumn(leftCol.getADQLName()))));
buf.append("=");
buf.append(getQualifiedTableName(rightCol.getTable())).append('.').append(getColumnName(rightCol));
buf.append(translate(generateJoinColumn(join.getRightTable(), rightCol, new ADQLColumn(rightCol.getADQLName()))));
}
}
......@@ -257,9 +210,9 @@ public class SQLServerTranslator extends JDBCTranslator {
// append the corresponding join condition:
if (buf.length() > 0)
buf.append(" AND ");
buf.append(getQualifiedTableName(leftCol.getTable())).append('.').append(getColumnName(leftCol));
buf.append(translate(generateJoinColumn(join.getLeftTable(), leftCol, usingCol)));
buf.append("=");
buf.append(getQualifiedTableName(rightCol.getTable())).append('.').append(getColumnName(rightCol));
buf.append(translate(generateJoinColumn(join.getRightTable(), rightCol, usingCol)));
}
sql.append("ON ").append(buf.toString());
......@@ -274,6 +227,32 @@ public class SQLServerTranslator extends JDBCTranslator {
return sql.toString();
}
/**
* Generate an ADQL column of the given table and with the given metadata.
*
* @param table Parent table of the column to generate.
* @param colMeta DB metadata of the column to generate.
* @param joinedColumn The joined column (i.e. the ADQL column listed in a
* USING) from which the generated column should
* derive.
* <i>If NULL, an {@link ADQLColumn} instance will be
* created from scratch using the ADQL name of the
* given DB metadata.</i>
*
* @return The generated column.
*/
protected ADQLColumn generateJoinColumn(final FromContent table, final DBColumn colMeta, final ADQLColumn joinedColumn){
ADQLColumn newCol = (joinedColumn == null ? new ADQLColumn(colMeta.getADQLName()) : new ADQLColumn(joinedColumn));
if (table != null){
if (table instanceof ADQLTable)
newCol.setAdqlTable((ADQLTable)table);
else
newCol.setAdqlTable(new ADQLTable(table.getName()));
}
newCol.setDBLink(colMeta);
return newCol;
}
@Override
public String translate(final ExtractCoord extractCoord) throws TranslationException{
return getDefaultADQLFunction(extractCoord);
......
......@@ -10,17 +10,13 @@ import org.junit.Before;
import org.junit.Test;
import adql.db.DBChecker;
import adql.db.DBColumn;
import adql.db.DBTable;
import adql.db.DefaultDBColumn;
import adql.db.DefaultDBTable;
import adql.db.SearchColumnList;
import adql.parser.ADQLParser;
import adql.parser.ParseException;
import adql.parser.SQLServer_ADQLQueryFactory;
import adql.query.ADQLQuery;
import adql.query.from.ADQLJoin;
import adql.query.operand.ADQLColumn;
public class TestSQLServerTranslator {
......@@ -50,7 +46,7 @@ public class TestSQLServerTranslator {
SQLServerTranslator translator = new SQLServerTranslator();
// Test the FROM part:
assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON \"aTable\".\"id\"=\"anotherTable\".\"id\" AND \"aTable\".\"name\"=\"anotherTable\".\"name\"", translator.translate(query.getFrom()));
assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON A.\"id\"=B.\"id\" AND A.\"name\"=B.\"name\"", translator.translate(query.getFrom()));
// Test the SELECT part (in order to ensure the usual common columns (due to NATURAL) are actually translated as columns of the first joined table):
assertEquals("SELECT A.\"id\" AS \"id\" , A.\"name\" AS \"name\" , A.\"aColumn\" AS \"aColumn\" , B.\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect()));
......@@ -64,58 +60,6 @@ public class TestSQLServerTranslator {
}
}
@Test
public void testNaturalJoin2(){
final String adqlquery = "SELECT id, name, aColumn, anotherColumn FROM aTable \"A\" NATURAL JOIN anotherTable B;";
try{
ADQLQuery query = (new ADQLParser(new DBChecker(tables), new SQLServer_ADQLQueryFactory())).parseQuery(adqlquery);
SQLServerTranslator translator = new SQLServerTranslator();
ADQLJoin join = (ADQLJoin)query.getFrom();
try{
StringBuffer buf = new StringBuffer();
// Find duplicated items between the two lists and translate them as ON conditions:
DBColumn rightCol;
SearchColumnList leftList = join.getLeftTable().getDBColumns();
SearchColumnList rightList = join.getRightTable().getDBColumns();
for(DBColumn leftCol : leftList){
// search for at most one column with the same name in the RIGHT list
// and throw an exception is there are several matches:
rightCol = ADQLJoin.findAtMostOneColumn(leftCol.getADQLName(), (byte)0, rightList, false);
// if there is one...
if (rightCol != null){
// ...check there is only one column with this name in the LEFT list,
// and throw an exception if it is not the case:
ADQLJoin.findExactlyOneColumn(leftCol.getADQLName(), (byte)0, leftList, true);
// ...append the corresponding join condition:
if (buf.length() > 0)
buf.append(" AND ");
ADQLColumn col = new ADQLColumn(leftCol.getADQLName());
col.setDBLink(leftCol);
// TODO col.setAdqlTable(adqlTable);
buf.append(translator.translate(col));
buf.append("=");
col = new ADQLColumn(rightCol.getADQLName());
col.setDBLink(rightCol);
buf.append(translator.translate(col));
}
}
System.out.println("ON " + buf.toString());
}catch(Exception uje){
System.err.println("Impossible to resolve the NATURAL JOIN between " + join.getLeftTable().toADQL() + " and " + join.getRightTable().toADQL() + "!");
uje.printStackTrace();
}
}catch(ParseException pe){
pe.printStackTrace();
fail("The given ADQL query is completely correct. No error should have occurred while parsing it. (see the console for more details)");
}
}
@Test
public void testJoinWithUSING(){
final String adqlquery = "SELECT B.id, name, aColumn, anotherColumn FROM aTable A JOIN anotherTable B USING(name);";
......@@ -125,7 +69,7 @@ public class TestSQLServerTranslator {
SQLServerTranslator translator = new SQLServerTranslator();
// Test the FROM part:
assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON \"aTable\".\"name\"=\"anotherTable\".\"name\"", translator.translate(query.getFrom()));
assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON A.\"name\"=B.\"name\"", translator.translate(query.getFrom()));
// Test the SELECT part (in order to ensure the usual common columns (due to USING) are actually translated as columns of the first joined table):
assertEquals("SELECT B.\"id\" AS \"id\" , A.\"name\" AS \"name\" , A.\"aColumn\" AS \"aColumn\" , B.\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect()));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment