From 04a00fe2973ef275389a34b7442504ddbc5ba5f0 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Wed, 13 Jul 2016 17:38:19 +0200
Subject: [PATCH] [ADQL] Fix a sub-query bug. No DBTable was set on an
 ADQLTable having a sub-query. Without this information, it was impossible to
 resolve columns makingreference to sub-queries of the clause FROM. See the
 JUnit test case for a concrete example. (error raised by Hendrik Heinl -
 ARI/GAVO)

---
 src/adql/query/from/ADQLTable.java      |  25 +-
 test/adql/db/TestSeveralSubQueries.java |  44 ++++
 test/adql/db/subquery_test_tables.xml   | 301 ++++++++++++++++++++++++
 3 files changed, 362 insertions(+), 8 deletions(-)
 create mode 100644 test/adql/db/TestSeveralSubQueries.java
 create mode 100644 test/adql/db/subquery_test_tables.xml

diff --git a/src/adql/query/from/ADQLTable.java b/src/adql/query/from/ADQLTable.java
index 0d592ea..baa2599 100644
--- a/src/adql/query/from/ADQLTable.java
+++ b/src/adql/query/from/ADQLTable.java
@@ -16,7 +16,7 @@ package adql.query.from;
  * 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-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -39,7 +39,7 @@ import adql.query.TextPosition;
  * A table reference may have an alias (MUST if it is a sub-query).
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 06/2015
+ * @version 2.1 (07/2016)
  */
 public class ADQLTable implements ADQLObject, FromContent {
 
@@ -165,6 +165,7 @@ public class ADQLTable implements ADQLObject, FromContent {
 	 * 
 	 * @return	The position of this {@link ADQLTable}.
 	 */
+	@Override
 	public final TextPosition getPosition(){
 		return position;
 	}
@@ -174,6 +175,7 @@ public class ADQLTable implements ADQLObject, FromContent {
 	 * 
 	 * @param pos	Position of this {@link ADQLTable}.
 	 */
+	@Override
 	public final void setPosition(final TextPosition pos){
 		position = pos;
 	}
@@ -465,18 +467,15 @@ public class ADQLTable implements ADQLObject, FromContent {
 	}
 
 	/**
-	 * <p>Sets the {@link DBTable} corresponding to this {@link ADQLTable}.</p>
-	 * <p><i>
-	 * 	<u>Note:</u> This function will do nothing if this {@link ADQLTable} is a sub query.
-	 * </i></p>
+	 * Sets the {@link DBTable} corresponding to this {@link ADQLTable}.
 	 * 
 	 * @param dbLink Its corresponding {@link DBTable}.
 	 */
 	public final void setDBLink(DBTable dbLink){
-		if (!isSubQuery())
-			this.dbLink = dbLink;
+		this.dbLink = dbLink;
 	}
 
+	@Override
 	public SearchColumnList getDBColumns(){
 		SearchColumnList list = new SearchColumnList();
 		if (isSubQuery() && dbLink == null)
@@ -488,12 +487,14 @@ public class ADQLTable implements ADQLObject, FromContent {
 		return list;
 	}
 
+	@Override
 	public ArrayList<ADQLTable> getTables(){
 		ArrayList<ADQLTable> tables = new ArrayList<ADQLTable>();
 		tables.add(this);
 		return tables;
 	}
 
+	@Override
 	public ArrayList<ADQLTable> getTablesByAlias(final String alias, final boolean caseSensitive){
 		ArrayList<ADQLTable> tables = new ArrayList<ADQLTable>();
 
@@ -515,19 +516,23 @@ public class ADQLTable implements ADQLObject, FromContent {
 		return tables;
 	}
 
+	@Override
 	public ADQLObject getCopy() throws Exception{
 		return new ADQLTable(this);
 	}
 
+	@Override
 	public String getName(){
 		return hasAlias() ? alias : (isSubQuery() ? "{subquery}" : getTableName());
 	}
 
+	@Override
 	public ADQLIterator adqlIterator(){
 		return new ADQLIterator(){
 
 			private boolean subQueryGot = !isSubQuery();
 
+			@Override
 			public ADQLObject next(){
 				if (!subQueryGot){
 					subQueryGot = true;
@@ -536,10 +541,12 @@ public class ADQLTable implements ADQLObject, FromContent {
 					throw new NoSuchElementException();
 			}
 
+			@Override
 			public boolean hasNext(){
 				return !subQueryGot;
 			}
 
+			@Override
 			public void replace(ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException{
 				if (!subQueryGot)
 					throw new IllegalStateException("replace(ADQLObject) impossible: next() has not yet been called !");
@@ -553,6 +560,7 @@ public class ADQLTable implements ADQLObject, FromContent {
 					throw new UnsupportedOperationException("Impossible to replace a sub-query (" + subQuery.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ") !");
 			}
 
+			@Override
 			public void remove(){
 				if (!subQueryGot)
 					throw new IllegalStateException("remove() impossible: next() has not yet been called !");
@@ -562,6 +570,7 @@ public class ADQLTable implements ADQLObject, FromContent {
 		};
 	}
 
+	@Override
 	public String toADQL(){
 		return (isSubQuery() ? ("(" + subQuery.toADQL() + ")") : getFullTableName()) + ((alias == null) ? "" : (" AS " + (isCaseSensitive(IdentifierField.ALIAS) ? ("\"" + alias + "\"") : alias)));
 	}
diff --git a/test/adql/db/TestSeveralSubQueries.java b/test/adql/db/TestSeveralSubQueries.java
new file mode 100644
index 0000000..bf9f485
--- /dev/null
+++ b/test/adql/db/TestSeveralSubQueries.java
@@ -0,0 +1,44 @@
+package adql.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import adql.parser.ADQLParser;
+import adql.query.ADQLQuery;
+import tap.metadata.TAPMetadata;
+import tap.metadata.TAPTable;
+import tap.metadata.TableSetParser;
+
+public class TestSeveralSubQueries {
+
+	@Before
+	public void setUp() throws Exception{}
+
+	@Test
+	public void test(){
+		try{
+			TableSetParser tsParser = new TableSetParser();
+			TAPMetadata esaMetaData = tsParser.parse(new File("test/adql/db/subquery_test_tables.xml"));
+			ArrayList<DBTable> esaTables = new ArrayList<DBTable>(esaMetaData.getNbTables());
+			Iterator<TAPTable> itTables = esaMetaData.getTables();
+			while(itTables.hasNext())
+				esaTables.add(itTables.next());
+
+			ADQLParser adqlParser = new ADQLParser(new DBChecker(esaTables));
+			ADQLQuery query = adqlParser.parseQuery("SELECT sel2.*,t1.h_m, t1.j_m, t1.k_m\nFROM (\n  SELECT sel1.*, t3.*\n  FROM (\n  	SELECT *\n    FROM table2 AS t2\n	WHERE 1=CONTAINS(POINT('ICRS', t2.ra, t2.dec), CIRCLE('ICRS', 56.75, 24.1167, 15.))\n  ) AS sel1 JOIN table3 AS t3 ON t3.oid2=sel1.oid2\n) AS sel2 JOIN table1 AS t1 ON sel2.oid=t1.oid");
+			assertEquals("SELECT sel2.* , t1.h_m , t1.j_m , t1.k_m\nFROM (SELECT sel1.* , t3.*\nFROM (SELECT *\nFROM table2 AS t2\nWHERE 1 = CONTAINS(POINT('ICRS', t2.ra, t2.dec), CIRCLE('ICRS', 56.75, 24.1167, 15.))) AS sel1 INNER JOIN table3 AS t3 ON ON t3.oid2 = sel1.oid2) AS sel2 INNER JOIN table1 AS t1 ON ON sel2.oid = t1.oid", query.toADQL());
+
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("No error expected! (see console for more details)");
+		}
+	}
+
+}
diff --git a/test/adql/db/subquery_test_tables.xml b/test/adql/db/subquery_test_tables.xml
new file mode 100644
index 0000000..526ef6e
--- /dev/null
+++ b/test/adql/db/subquery_test_tables.xml
@@ -0,0 +1,301 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<vod:tableset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vod="http://www.ivoa.net/xml/VODataService/v1.1" xsi:type="vod:TableSet" xsi:schemaLocation="http://www.ivoa.net/xml/VODataService/v1.1 http://www.ivoa.net/xml/VODataService/v1.1">
+	<schema>
+		<name>public</name>
+		<table type="base_table">
+			<name>table1</name>
+			<column std="false">
+				<name>oid</name>
+				<unit></unit>
+				<ucd>meta.id;meta.main</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">BIGINT</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>ra</name>
+				<unit>deg</unit>
+				<ucd>pos.eq.ra;meta.main</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>dec</name>
+				<unit>Angle[deg]</unit>
+				<ucd>pos.eq.dec;meta.main</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>h_m</name>
+				<unit>mag</unit>
+				<ucd>phot.mag;em.IR.H</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">REAL</dataType>
+			</column>
+			<column std="false">
+				<name>j_m</name>
+				<unit>mag</unit>
+				<ucd>phot.mag;em.IR.J</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">REAL</dataType>
+			</column>
+			<column std="false">
+				<name>k_m</name>
+				<unit>mag</unit>
+				<ucd>phot.mag;em.IR.K</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">REAL</dataType>
+			</column>
+		</table>
+		<table type="base_table">
+			<name>table2</name>
+			<column std="false">
+				<name>oid2</name>
+				<unit></unit>
+				<ucd>meta.id;meta.main</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">BIGINT</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>ra</name>
+				<unit>deg</unit>
+				<ucd>pos.eq.ra;meta.main</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>ra_error</name>
+				<unit>mas</unit>
+				<ucd>stat.error;pos.eq.ra</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+			</column>
+			<column std="false">
+				<name>dec</name>
+				<unit>deg</unit>
+				<ucd>pos.eq.dec;meta.main</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>dec_error</name>
+				<unit>mas</unit>
+				<ucd>stat.error;pos.eq.dec</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+			</column>
+			<column std="false">
+				<name>parallax</name>
+				<unit>mas</unit>
+				<ucd>pos.parallax</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>parallax_error</name>
+				<unit>mas</unit>
+				<ucd>stat.error;pos.parallax</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>magnitude</name>
+				<unit>mag</unit>
+				<ucd>phot.mag;stat.mean;em.opt</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>pmdec</name>
+				<unit>mas/year</unit>
+				<ucd>pos.pm;pos.eq.dec</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>pmdec_error</name>
+				<unit>mas/year</unit>
+				<ucd>stat.error;pos.pm;pos.eq.dec</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+			</column>
+			<column std="false">
+				<name>pmra</name>
+				<unit>mas/year</unit>
+				<ucd>pos.pm;pos.eq.ra</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+				<flag>indexed</flag>
+			</column>
+			<column std="false">
+				<name>pmra_error</name>
+				<unit>mas/year</unit>
+				<ucd>stat.error;pos.pm;pos.eq.ra</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">DOUBLE</dataType>
+			</column>
+		</table>
+		<table type="base_table">
+			<name>table3</name>
+			<column std="false">
+				<name>oid</name>
+				<unit></unit>
+				<ucd>meta.id</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">BIGINT</dataType>
+			</column>
+			<column std="false">
+				<name>oid2</name>
+				<description></description>
+				<unit></unit>
+				<ucd>meta.id</ucd>
+				<utype></utype>
+				<dataType xsi:type="vod:TAPType">BIGINT</dataType>
+			</column>
+		</table>
+	</schema>
+	<schema>
+		<name>tap_schema</name>
+		<table type="base_table">
+			<name>tables</name>
+			<column std="false">
+				<name>description</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>schema_name</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>size</name>
+				<dataType xsi:type="vod:TAPType">INTEGER</dataType>
+			</column>
+			<column std="false">
+				<name>table_name</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>table_type</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>utype</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+		</table>
+		<table type="base_table">
+			<name>columns</name>
+			<column std="false">
+				<name>column_name</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>datatype</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>description</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>indexed</name>
+				<dataType xsi:type="vod:TAPType">INTEGER</dataType>
+			</column>
+			<column std="false">
+				<name>principal</name>
+				<dataType xsi:type="vod:TAPType">INTEGER</dataType>
+			</column>
+			<column std="false">
+				<name>schema_name</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>size</name>
+				<dataType xsi:type="vod:TAPType">INTEGER</dataType>
+			</column>
+			<column std="false">
+				<name>std</name>
+				<dataType xsi:type="vod:TAPType">INTEGER</dataType>
+			</column>
+			<column std="false">
+				<name>table_name</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>ucd</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>unit</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>utype</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+		</table>
+		<table type="base_table">
+			<name>keys</name>
+			<column std="false">
+				<name>description</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>from_table</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>key_id</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>target_table</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>utype</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+		</table>
+		<table type="base_table">
+			<name>schemas</name>
+			<column std="false">
+				<name>description</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>schema_name</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>utype</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+		</table>
+		<table type="base_table">
+			<name>key_columns</name>
+			<column std="false">
+				<name>from_column</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>key_id</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+			<column std="false">
+				<name>target_column</name>
+				<dataType xsi:type="vod:TAPType">VARCHAR</dataType>
+			</column>
+		</table>
+	</schema>
+</vod:tableset>
-- 
GitLab