Commit 8de388e5 authored by gmantele's avatar gmantele
Browse files

[TAP] Add coordinate system information in TAP_SCHEMA and VOTable (cf COOSYS).

parent f7989175
Loading
Loading
Loading
Loading
+116 −1
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import tap.data.ResultSetTableIterator;
import tap.data.TableIterator;
import tap.log.TAPLog;
import tap.metadata.TAPColumn;
import tap.metadata.TAPCoosys;
import tap.metadata.TAPForeignKey;
import tap.metadata.TAPMetadata;
import tap.metadata.TAPMetadata.STDSchema;
@@ -938,10 +939,18 @@ public class JDBCConnection implements DBConnection {
				logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.tables.", null);
			List<TAPTable> lstTables = loadTables(tap_schema.getTable(STDTable.TABLES.label), metadata, stmt);

			// load all coordinate systems from TAP_SCHEMA.coosys: [non standard]
			Map<String, TAPCoosys> mapCoosys = null;
			if (isTableExisting(tap_schema.getDBName(), STDTable.COOSYS.label, stmt.getConnection().getMetaData())){
				if (logger != null)
					logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.coosys.", null);
				mapCoosys = loadCoosys(tap_schema.getTable(STDTable.COOSYS.label), metadata, stmt);
			}

			// load all columns from TAP_SCHEMA.columns:
			if (logger != null)
				logger.logDB(LogLevel.INFO, this, "LOAD_TAP_SCHEMA", "Loading TAP_SCHEMA.columns.", null);
			loadColumns(tap_schema.getTable(STDTable.COLUMNS.label), lstTables, stmt);
			loadColumns(tap_schema.getTable(STDTable.COLUMNS.label), lstTables, mapCoosys, stmt);

			// load all foreign keys from TAP_SCHEMA.keys and TAP_SCHEMA.key_columns:
			if (logger != null)
@@ -1147,6 +1156,62 @@ public class JDBCConnection implements DBConnection {
		}
	}
	
	/**
	 * Load all coordinate systems declared in the TAP_SCHEMA.
	 * 
	 * @param tableDef		Definition of the table TAP_SCHEMA.coosys.
	 * @param metadata		Metadata in which the found coordinate systems will be inserted (see {@link TAPMetadata#addCoosys(TAPCoosys)}).
	 * @param stmt			Statement to use in order to interact with the database.
	 * 
	 * @return	A map containing all declared coordinate systems (key=coosys ID, value={@link TAPCoosys}).
	 *        	<i>note: this map is required by {@link #loadColumns(TAPTable, List, Map, Statement)}.</i>
	 * 
	 * @throws DBException	If any error occurs while interacting with the database.
	 * 
	 * @since 2.1
	 */
	protected Map<String, TAPCoosys> loadCoosys(final TAPTable tableDef, final TAPMetadata metadata, final Statement stmt) throws DBException{
		ResultSet rs = null;
		try{
			// Build the SQL query:
			StringBuffer sqlBuf = new StringBuffer("SELECT ");
			sqlBuf.append(translator.getColumnName(tableDef.getColumn("id")));
			sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("system")));
			sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("equinox")));
			sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("epoch")));
			sqlBuf.append(" FROM ").append(translator.getTableName(tableDef, supportsSchema));
			sqlBuf.append(" ORDER BY 1,2,3,4");

			// Execute the query:
			rs = stmt.executeQuery(sqlBuf.toString());

			// Create all coosys:
			HashMap<String, TAPCoosys> mapCoosys = new HashMap<String, TAPCoosys>();
			while(rs.next()){
				String coosysId = rs.getString(1),
						system = rs.getString(2), equinox = rs.getString(3),
						epoch = rs.getString(4);

				// create the new coosys:
				TAPCoosys newCoosys = new TAPCoosys(coosysId, system, nullifyIfNeeded(equinox), nullifyIfNeeded(epoch));
				
				// create and add the new coosys:
				metadata.addCoosys(newCoosys);
				mapCoosys.put(coosysId, newCoosys);
			}

			return mapCoosys;
		}catch(SQLException se){
			if (!isCancelled() && logger != null)
				logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load coordinate systems from TAP_SCHEMA.coosys!", se);
			throw new DBException("Impossible to load coordinate systems from TAP_SCHEMA.coosys!", se);
		}finally{
			close(rs);
		}
	}
	


	/**
	 * <p>Load into the corresponding tables all columns listed in TAP_SCHEMA.columns.</p>
	 * 
@@ -1165,8 +1230,38 @@ public class JDBCConnection implements DBConnection {
	 * @param stmt			Statement to use in order to interact with the database.
	 * 
	 * @throws DBException	If a table can not be found, or if any other error occurs while interacting with the database.
	 * 
	 * @deprecated	This method is now replaced by {@link #loadColumns(TAPTable, List, Map, Statement)} which has an additional parameter:
	 *            	the list of declared coordinate systems. 
	 */
	@Deprecated
	protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Statement stmt) throws DBException{
		loadColumns(tableDef, lstTables, null, stmt);
	}

	/**
	 * <p>Load into the corresponding tables all columns listed in TAP_SCHEMA.columns.</p>
	 * 
	 * <p><i>Note:
	 * 	Tables are searched in the given list by their ADQL name and case sensitively.
	 * 	If they can not be found a {@link DBException} is thrown.
	 * </i></p>
	 * 
	 * <p><i>Note 2:
	 * 	If the column column_index exists, column entries are retrieved ordered by ascending table_name, then column_index, and finally column_name.
	 * 	If this column does not exist, column entries are retrieved ordered by ascending table_name and then column_name.
	 * </i></p>
	 * 
	 * @param tableDef		Definition of the table TAP_SCHEMA.columns.
	 * @param lstTables		List of all published tables (= all tables listed in TAP_SCHEMA.tables).
	 * @param mapCoosys		List of all published coordinate systems (= all coordinates systems listed in TAP_SCHEMA.coosys).
	 * @param stmt			Statement to use in order to interact with the database.
	 * 
	 * @throws DBException	If a table can not be found, or if any other error occurs while interacting with the database.
	 * 
	 * @since 2.1
	 */
	protected void loadColumns(final TAPTable tableDef, final List<TAPTable> lstTables, final Map<String, TAPCoosys> mapCoosys, final Statement stmt) throws DBException{
		ResultSet rs = null;
		try{
			// Determine whether the dbName column exists:
@@ -1178,6 +1273,9 @@ public class JDBCConnection implements DBConnection {
			// Determine whether the columnIndex column exists:
			boolean hasColumnIndex = isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), "column_index", connection.getMetaData());

			// Determine whether the coosys_id column exists:
			boolean hasCoosys = (mapCoosys != null) && isColumnExisting(tableDef.getDBSchemaName(), tableDef.getDBName(), "coosys_id", connection.getMetaData());

			// Build the SQL query:
			StringBuffer sqlBuf = new StringBuffer("SELECT ");
			sqlBuf.append(translator.getColumnName(tableDef.getColumn("table_name")));
@@ -1200,6 +1298,8 @@ public class JDBCConnection implements DBConnection {
				sqlBuf.append(", ");
				translator.appendIdentifier(sqlBuf, DB_NAME_COLUMN, IdentifierField.COLUMN);
			}
			if (hasCoosys)
				sqlBuf.append(", ").append(translator.getColumnName(tableDef.getColumn("coosys_id")));
			sqlBuf.append(" FROM ").append(translator.getTableName(tableDef, supportsSchema));
			if (hasColumnIndex)
				sqlBuf.append(" ORDER BY 1,12,2");
@@ -1254,6 +1354,21 @@ public class JDBCConnection implements DBConnection {
				newColumn.setDBName(dbName);
				newColumn.setIndex(colIndex);
				
				// set the coordinate system if any is specified:
				if (hasCoosys){
					int indCoosys = 12; 
					if (hasColumnIndex)
						indCoosys++;
					if (hasDBName)
						indCoosys++;
					String coosysId = rs.getString(indCoosys);
					if (coosysId != null){
						newColumn.setCoosys(mapCoosys.get(coosysId));
						if (logger != null && newColumn.getCoosys() == null)
							logger.logDB(LogLevel.WARNING, this, "LOAD_TAP_SCHEMA", "No coordinate system for the column \""+columnName+"\"! Cause: unknown coordinate system: \""+coosysId+"\".", null);
					}
				}

				// add the new column inside its corresponding table:
				table.addColumn(newColumn);
			}
+33 −8
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

@@ -35,16 +36,17 @@ import tap.data.DataReadException;
import tap.data.TableIterator;
import tap.error.DefaultTAPErrorWriter;
import tap.metadata.TAPColumn;
import tap.metadata.TAPCoosys;
import tap.metadata.VotType;
import tap.metadata.VotType.VotDatatype;
import uk.ac.starlink.table.AbstractStarTable;
import uk.ac.starlink.table.ColumnInfo;
import uk.ac.starlink.table.DefaultValueInfo;
import uk.ac.starlink.table.DescribedValue;
import uk.ac.starlink.table.RowSequence;
import uk.ac.starlink.table.StarTable;
import uk.ac.starlink.votable.DataFormat;
import uk.ac.starlink.votable.VOSerializer;
import uk.ac.starlink.votable.VOStarTable;
import uk.ac.starlink.votable.VOTableVersion;
import adql.db.DBColumn;
import adql.db.DBType;
@@ -404,11 +406,30 @@ public class VOTableFormat implements OutputFormat {
			out.newLine();
		}
		
		/* TODO Add somewhere in the table header the different Coordinate Systems used in this result!
		 * 2 ways to do so:
		 * 	1/ COOSYS (deprecated from VOTable 1.2, but soon un-deprecated)
		 * 	2/ a GROUP item with the STC expression of the coordinate system. 
		 */
		// Insert the definition of all used coordinate systems:
		HashSet<String> insertedCoosys = new HashSet<String>(10);
		for(DBColumn col : execReport.resultingColumns){
			// ignore columns with no coossys:
			if (col instanceof TAPColumn && ((TAPColumn)col).getCoosys() != null){
				// get its coosys:
				TAPCoosys coosys = ((TAPColumn)col).getCoosys();
				// insert the coosys definition ONLY if not already done because of another column:
				if (!insertedCoosys.contains(coosys.getId())){
					// write the VOTable serialization of this coordinate system definition:
					out.write("<COOSYS"+VOSerializer.formatAttribute("ID", coosys.getId()));
					if (coosys.getSystem() != null)
						out.write(VOSerializer.formatAttribute("system", coosys.getSystem()));
					if (coosys.getEquinox() != null)
						out.write(VOSerializer.formatAttribute("equinox", coosys.getEquinox()));
					if (coosys.getEpoch() != null)
						out.write(VOSerializer.formatAttribute("epoch", coosys.getEpoch()));
					out.write(" />");
					out.newLine();
					// remember this coosys has already been written:
					insertedCoosys.add(coosys.getId());
				}
			}
		}

		out.flush();
	}
@@ -502,13 +523,17 @@ public class VOTableFormat implements OutputFormat {

		// Set the XType (if any):
		if (votType.xtype != null)
			colInfo.setAuxDatum(new DescribedValue(new DefaultValueInfo("xtype", String.class, "VOTable xtype attribute"), votType.xtype));
			colInfo.setAuxDatum(new DescribedValue(VOStarTable.XTYPE_INFO, votType.xtype));

		// Set the additional information: unit, UCD and UType:
		colInfo.setUnitString(tapCol.getUnit());
		colInfo.setUCD(tapCol.getUcd());
		colInfo.setUtype(tapCol.getUtype());
		
		// Set the coosys ref (if any):
		if (tapCol.getCoosys() != null)
			colInfo.setAuxDatum(new DescribedValue(VOStarTable.REF_INFO, tapCol.getCoosys().getId()));

		return colInfo;
	}

+29 −2
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package tap.metadata;
 * You should have received a copy of the GNU Lesser General Public License
 * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

@@ -69,7 +69,7 @@ import adql.db.DBType.DBDatatype;
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.1 (07/2016)
 * @version 2.1 (07/2017)
 */
public class TAPColumn implements DBColumn {

@@ -133,6 +133,11 @@ public class TAPColumn implements DBColumn {
	 * @since 2.1 */
	private int index = -1;
	
	/** Coordinate system used by this column values.
	 * <i>Note: Of course, this attribute has to be set only on coordinate columns.</i>
	 * @since 2.1 */
	private TAPCoosys coosys = null;

	/** Let add some information in addition of the ones of the TAP protocol.
	 * <i>Note: This object can be anything: an {@link Integer}, a {@link String}, a {@link Map}, a {@link List}, ...
	 * Its content is totally free and never used or checked.</i> */
@@ -770,6 +775,28 @@ public class TAPColumn implements DBColumn {
		this.index = columnIndex;
	}

	/**
	 * Get the used coordinate system.
	 * 
	 * @return	Its coordinate system.
	 * 
	 * @since 2.1
	 */
	public final TAPCoosys getCoosys(){
		return coosys;
	}

	/**
	 * Set the the coordinate system to use.
	 * 
	 * @param newCoosys	Its new coordinate system.
	 * 
	 * @since 2.1
	 */
	public final void setCoosys(final TAPCoosys newCoosys){
		this.coosys = newCoosys;
	}

	/**
	 * <p>Get the other (piece of) information associated with this column.</p>
	 * 
+175 −0
Original line number Diff line number Diff line
package tap.metadata;

/*
 * This file is part of TAPLibrary.
 * 
 * TAPLibrary 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.
 * 
 * TAPLibrary 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 TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2017 - Astronomisches Rechen Institut (ARI)
 */

/**
 * Definition of a coordinate system.
 * 
 * <p>
 * 	This object just annotates a {@link TAPColumn} object (see {@link TAPColumn#getCoosys()}
 * 	and {@link TAPColumn#setCoosys(TAPCoosys)}). Its only purpose is to enrich the VOTable
 * 	metadata so that VO clients (like Aladin and TOPCAT) can use the coordinates the most
 * 	precisely as possible.
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (ARI)
 * @version 2.1 (07/2017)
 * @since 2.1
 */
public class TAPCoosys {

	/** ID of this coordinate system definition.
	 * <p>It is particularly used in the VOTable to associate columns with it.</p>
	 * <i>Note: This attribute can NOT be NULL.</i> */
	protected final String id;
	
	/** Name of the coordinate system.
	 * <p>
	 * 	It should be a value among:
	 * 	"ICRS", "eq FK5", "eq FK4", "ecl FK4", "ecl FK5",
	 * 	"galactic", "supergalactic", "barycentric", "geo app"
	 * 	and a user-defined "xy" value.
	 * </p> */
	protected final String system;
	
	/** Equinox of this coordinate system.
	 * <p>
	 * 	This parameter required to fix the equatorial or ecliptic systems
	 * 	(as e.g. "J2000" as the default "eq FK5" or "B1950" as the default "eq FK4").
	 * </p> */
	protected String equinox;
	
	/** Epoch at which the coordinates were measured. */
	protected String epoch;
	
	/**
	 * Create a minimum coordinate system definition.
	 * 
	 * @param id		ID of the definition to create. <i>(must NOT be NULL)</i>
	 * @param system	Coordinate system. <i>(must NOT be NULL)</i>
	 * 
	 * @throws NullPointerException	If any of the given parameters is NULL or an empty string.
	 */
	public TAPCoosys(final String id, final String system) throws NullPointerException {
		this(id, system, null, null);
	}
	
	/**
	 * Create a coordinate system definition.
	 * 
	 * <p>
	 * 	Only the ID and the system are required.
	 * 	The equinox and especially the epoch are optional.
	 * </p>
	 * 
	 * @param id		ID of the definition to create. <i>(must NOT be NULL)</i>
	 * @param system	Coordinate system. <i>(must NOT be NULL)</i>
	 * @param equinox	Equinox of this coordinate system.
	 * @param epoch		Epoch at which the coordinates were measured.
	 * 
	 * @throws NullPointerException	If the ID or the system is NULL or an empty string.
	 */
	public TAPCoosys(final String id, final String system, final String equinox, final String epoch) throws NullPointerException {
		if (id == null || id.trim().length() == 0)
			throw new NullPointerException("Missing Coosys ID!");
		this.id = id;
		
		if (system == null || system.trim().length() == 0)
			throw new NullPointerException("Missing coordinate system!");
		this.system = system;
		
		this.equinox = equinox;
		this.epoch = epoch;
	}

	/**
	 * Get the ID of this coordinate system definition.
	 * 
	 * @return	Its ID. <i>(can NOT be NULL)</i>
	 */
	public final String getId() {
		return id;
	}

	/**
	 * Get the coordinate system of this definition.
	 * 
	 * @return	Its system. <i>(can NOT be NULL)</i>
	 */
	public final String getSystem() {
		return system;
	}

	/**
	 * Get the equinox of this coordinate system.
	 * 
	 * @return	Its equinox. <i>(may be NULL)</i>
	 */
	public final String getEquinox() {
		return equinox;
	}

	/**
	 * Set the equinox of this coordinate system.
	 * 
	 * @param equinox	Its new equinox. <i>(may be NULL)</i>
	 */
	public void setEquinox(final String equinox) {
		this.equinox = equinox;
	}

	/**
	 * Get the epoch at which the coordinates were measured.
	 * 
	 * @return	Its equinox. <i>(may be NULL)</i>
	 */
	public final String getEpoch() {
		return epoch;
	}

	/**
	 * Set the epoch at which the coordinates were measured.
	 * 
	 * @param epoch	Its new epoch. <i>(may be NULL)</i>
	 */
	public void setEpoch(final String epoch) {
		this.epoch = epoch;
	}
	
	@Override
	public int hashCode() {
		return id.hashCode();
	}

	@Override
	public boolean equals(Object obj){
		if (!(obj instanceof TAPCoosys))
			return false;

		TAPCoosys coosys = (TAPCoosys)obj;
		return coosys.getId().equals(getId());
	}

	@Override
	public String toString(){
		return id;
	}
	
}
+79 −3
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ package tap.metadata;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -64,7 +65,7 @@ import uws.UWSToolBox;
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.1 (03/2017)
 * @version 2.1 (07/2017)
 */
public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResource {

@@ -75,6 +76,10 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
	/** List of all schemas available through the TAP service. */
	protected final Map<String,TAPSchema> schemas;
	
	/** List of all coordinate systems used by columns published in the TAP_SCHEMA.
	 * @since 2.1 */
	protected final Map<String, TAPCoosys> coordinateSystems;

	/** Part of the TAP URI which identify this TAP resource.
	 * By default, it is the resource name ; so here, the corresponding TAP URI would be: "/tables". */
	protected String accessURL = getName();
@@ -107,6 +112,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
	 */
	public TAPMetadata(){
		schemas = new LinkedHashMap<String,TAPSchema>();
		coordinateSystems = new HashMap<String, TAPCoosys>();
	}

	/**
@@ -424,6 +430,60 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
		return nbTables;
	}
	
	/**
	 * Get the coordinate system definition associated with the given ID.
	 * 
	 * @param coosysId	ID of the coordinate system to get. <i>(case sensitive)</i>
	 * 
	 * @return	The corresponding coordinate system definition,
	 *        	or NULL if no match for the given ID.
	 * 
	 * @since 2.1
	 */
	public TAPCoosys getCoosys(final String coosysId) {
		return (coosysId == null ? null : coordinateSystems.get(coosysId));
	}
	
	/**
	 * Add the given coordinate system definition.
	 * 
	 * <p><b>Important:</b>
	 * 	If a coordinate system with the same ID (case sensitive) is already declared in
	 * 	this {@link TAPMetadata}, it will be replaced by the given one. The replaced
	 * 	coordinate system is returned by this function.
	 * </p>
	 * 
	 * @param newCoosys	The coordinate system definition to add.
	 * 
	 * @return	The coordinate system definition previously declared with the same ID in this {@link TAPMetadata},
	 *        	or NULL if no coord. sys. was declared with this ID.  
	 * 
	 * @since 2.1
	 */
	public TAPCoosys addCoosys(final TAPCoosys newCoosys) {
		if (newCoosys == null)
			return null;
		else{
			TAPCoosys formerValue = coordinateSystems.get(newCoosys.getId());
			coordinateSystems.put(newCoosys.getId(), newCoosys);
			return formerValue;
		}
	}
	
	/**
	 * Remove the coordinate system declared with the given ID.
	 * 
	 * @param coosysId	The ID of the coordinate system definition to remove.
	 * 
	 * @return	The removed coordinate system definition,
	 *        	or NULL if none is declared with the given ID.
	 * 
	 * @since 2.1
	 */
	public TAPCoosys removeCoosys(final String coosysId){
		return (coosysId == null) ? null : coordinateSystems.remove(coosysId);
	}

	/**
	 * Let iterating over the list of all tables contained in a given {@link TAPMetadata} object.
	 * 
@@ -870,6 +930,14 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
				tables.addColumn("utype", new DBType(DBDatatype.VARCHAR), "UTYPE if table corresponds to a data model", null, null, null, false, false, true);
				return tables;

			case COOSYS:
				TAPTable coosys = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.COOSYS, TableType.table, "List of coordinate systems published used this TAP service.", null);
				coosys.addColumn("id", new DBType(DBDatatype.VARCHAR), "ID of the coordinate system definition.", null, null, null, true, true, false);
				coosys.addColumn("system", new DBType(DBDatatype.VARCHAR), "The coordinate system (among \"ICRS\", \"eq_FK5\", \"eq_FK4\", \"ecl_FK4\", \"ecl_FK5\", \"galactic\", \"supergalactic\").", null, null, null, false, false, false);
				coosys.addColumn("equinox", new DBType(DBDatatype.VARCHAR), "Required to fix the equatorial or ecliptic systems (as e.g. \"J2000\" as the default \"eq_FK5\" or \"B1950\" as the default \"eq_FK4\").", null, null, null, false, false, false);
				coosys.addColumn("epoch", new DBType(DBDatatype.VARCHAR), "Epoch of the positions (if necessary).", null, null, null, false, false, false);
				return coosys;

			case COLUMNS:
				TAPTable columns = new TAPTable(STDSchema.TAPSCHEMA + "." + STDTable.COLUMNS, TableType.table, "List of columns of all tables listed in TAP_SCHEMA.TABLES and published in this TAP service.", null);
				columns.addColumn("column_index", new DBType(DBDatatype.INTEGER), "this index is used to recommend column ordering for clients", null, null, null, false, false, true);
@@ -885,6 +953,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
				columns.addColumn("indexed", new DBType(DBDatatype.INTEGER), "an indexed column; 1 means true, 0 means false", null, null, null, false, false, true);
				columns.addColumn("principal", new DBType(DBDatatype.INTEGER), "a principal column; 1 means true, 0 means false", null, null, null, false, false, true);
				columns.addColumn("std", new DBType(DBDatatype.INTEGER), "a standard column; 1 means true, 0 means false", null, null, null, false, false, true);
				columns.addColumn("coosys_id", new DBType(DBDatatype.VARCHAR), "ID of the used coordinate systems (if any).", null, null, null, false, false, false);
				return columns;

			case KEYS:
@@ -961,11 +1030,18 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
	 * Enumeration of all tables of TAP_SCHEMA.
	 * 
	 * @author Gr&eacute;gory Mantelet (ARI)
	 * @version 2.0 (07/2014)
	 * @version 2.1 (07/2017)
	 * @since 2.0
	 */
	public enum STDTable{
		SCHEMAS("schemas"), TABLES("tables"), COLUMNS("columns"), KEYS("keys"), KEY_COLUMNS("key_columns");
		SCHEMAS("schemas"),
		TABLES("tables"),
		COLUMNS("columns"),
		KEYS("keys"),
		KEY_COLUMNS("key_columns"),
		/** Non standard table but anyway expected by this library in order to associated coordinate columns with a coordinate system.
		 * @since 2.1 */
		COOSYS("coosys");

		/** Real name of the table. */
		public final String label;