Commit a2fb29ad authored by gmantele's avatar gmantele
Browse files

[ADQL] Add an ADQL translator for MS SQL Server. This particular translator

deals with NATURAL JOINs and JOINs using the keyword USING so that being
supported by SQL Server. Basically, they are translated as a list of ON
conditions.
Warning: This translator is just guaranteed to solve the NATURAL and USING
issue. Support for datatypes conversion and case sensitivity has to be
reviewed. Besides no geometrical function is translated for SQL Server.
parent b270eed3
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
package adql.parser;

/*
 * 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 - Astronomisches Rechen Institut (ARI)
 */

import adql.query.from.ADQLJoin;
import adql.query.from.CrossJoin;
import adql.query.from.FromContent;
import adql.query.from.InnerJoin;
import adql.query.from.OuterJoin;
import adql.query.from.SQLServer_InnerJoin;
import adql.query.from.SQLServer_OuterJoin;
import adql.query.from.OuterJoin.OuterType;
import adql.translator.SQLServerTranslator;

/**
 * <p>Special extension of {@link ADQLQueryFactory} for MS SQL Server.</p>
 * 
 * <p><b>Important:</b>
 * 	This class is generally used when an ADQL translator for MS SQL Server is needed.
 * 	See {@link SQLServerTranslator} for more details.
 * </p>
 * 
 * <p>
 * 	The only difference with {@link ADQLQueryFactory} is the creation of an
 * 	{@link ADQLJoin}. Instead of creating {@link InnerJoin} and {@link OuterJoin},
 * 	{@link SQLServer_InnerJoin} and {@link SQLServer_OuterJoin} are respectively created.
 * 	The only difference between these last classes and the first ones is in the processing
 * 	of NATURAL JOINs and JOINs using the keyword USING.
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (ARI)
 * @version 1.4 (03/2016)
 * @since 1.4
 * 
 * @see SQLServer_InnerJoin
 * @see SQLServer_OuterJoin
 * @see SQLServerTranslator
 */
public class SQLServer_ADQLQueryFactory extends ADQLQueryFactory {

	public ADQLJoin createJoin(JoinType type, FromContent leftTable, FromContent rightTable) throws Exception{
		switch(type){
			case CROSS:
				return new CrossJoin(leftTable, rightTable);
			case INNER:
				return new SQLServer_InnerJoin(leftTable, rightTable);
			case OUTER_LEFT:
				return new SQLServer_OuterJoin(leftTable, rightTable, OuterType.LEFT);
			case OUTER_RIGHT:
				return new SQLServer_OuterJoin(leftTable, rightTable, OuterType.RIGHT);
			case OUTER_FULL:
				return new SQLServer_OuterJoin(leftTable, rightTable, OuterType.FULL);
			default:
				throw new Exception("Unknown join type: " + type);
		}
	}
	
}
 No newline at end of file
+206 −0
Original line number Diff line number Diff line
package adql.query.from;

/*
 * 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 - Astronomisches Rechen Institut (ARI)
 */

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import adql.db.DBColumn;
import adql.db.DBCommonColumn;
import adql.db.SearchColumnList;
import adql.db.exception.UnresolvedJoinException;
import adql.parser.SQLServer_ADQLQueryFactory;
import adql.query.ClauseConstraints;
import adql.query.IdentifierField;
import adql.query.operand.ADQLColumn;

/**
 * <p>Special implementation of {@link InnerJoin} for MS SQL Server.</p>
 * 
 * <p><b>Important:</b>
 * 	Instances of this class are created only by {@link SQLServer_ADQLQueryFactory}.
 * </p>
 * 
 * <p>
 * 	This implementation just changes the behavior the {@link #getDBColumns()}.
 * 	In MS SQL Server, there is no keyword NATURAL and USING. That's why the {@link DBColumn}s
 * 	returned by {@link DBColumn} can not contain any {@link DBCommonColumn}. Instead,
 * 	the {@link DBColumn} of the first joined table (i.e. the left one) is returned.
 * </p>
 * 
 * <p>
 * 	Since this special behavior is also valid for {@link OuterJoin}, a special implementation
 * 	of this class has been also created: {@link SQLServer_OuterJoin}. Both must have exactly the
 * 	same behavior when {@link #getDBColumns()} is called. That's why the static function
 * 	{@link #getDBColumns(ADQLJoin)} has been created. It is called by {@link SQLServer_InnerJoin}
 * 	and {@link SQLServer_OuterJoin}.
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (ARI)
 * @version 1.4 (03/2016)
 * @since 1.4
 * 
 * @see SQLServer_ADQLQueryFactory
 */
public class SQLServer_InnerJoin extends InnerJoin {

	/**
	 * Builds a NATURAL INNER JOIN between the two given "tables".
	 * 
	 * @param left	Left "table".
	 * @param right	Right "table".
	 * 
	 * @see InnerJoin#InnerJoin(FromContent, FromContent)
	 */
	public SQLServer_InnerJoin(FromContent left, FromContent right) {
		super(left, right);
	}

	/**
	 * Builds an INNER JOIN between the two given "tables" with the given condition.
	 * 
	 * @param left		Left "table".
	 * @param right		Right "table".
	 * @param condition	Join condition.
	 * 
	 * @see InnerJoin#InnerJoin(FromContent, FromContent, ClauseConstraints)
	 */
	public SQLServer_InnerJoin(FromContent left, FromContent right, ClauseConstraints condition) {
		super(left, right, condition);
	}

	/**
	 * Builds an INNER JOIN between the two given "tables" with the given condition.
	 * 
	 * @param left		Left "table".
	 * @param right		Right "table".
	 * @param condition	Join condition.
	 * 
	 * @see InnerJoin#InnerJoin(FromContent, FromContent, Collection)
	 */
	public SQLServer_InnerJoin(FromContent left, FromContent right, Collection<ADQLColumn> lstColumns) {
		super(left, right, lstColumns);
	}

	/**
	 * Builds a copy of the given INNER join.
	 * 
	 * @param toCopy		The INNER join to copy.
	 * 
	 * @throws Exception	If there is an error during the copy.
	 * 
	 * @see InnerJoin#InnerJoin(InnerJoin)
	 */
	public SQLServer_InnerJoin(InnerJoin toCopy) throws Exception {
		super(toCopy);
	}

	@Override
	public SearchColumnList getDBColumns() throws UnresolvedJoinException {
		return getDBColumns(this);
	}

	/**
	 * <p>Gets the list of all columns (~ database metadata) available in this FROM part.
	 * Columns implied in a NATURAL join or in a USING list, are not returned as a {@link DBCommonColumn} ; 
	 * actually, just the corresponding {@link DBColumn} of the left table is returned.</p>
	 * 
	 * @return	All the available {@link DBColumn}s.
	 * @throws UnresolvedJoinException If a join is not possible.
	 */
	public static SearchColumnList getDBColumns(final ADQLJoin join) throws UnresolvedJoinException{
		try{
			SearchColumnList list = new SearchColumnList();
			SearchColumnList leftList = join.getLeftTable().getDBColumns();
			SearchColumnList rightList = join.getRightTable().getDBColumns();

			/* 1. Figure out duplicated columns */
			HashMap<String,DBColumn> mapDuplicated = new HashMap<String,DBColumn>();
			// CASE: NATURAL
			if (join.isNatural()){
				// Find duplicated items between the two lists and add one common column in mapDuplicated for each
				DBColumn rightCol;
				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 = 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:
						findExactlyOneColumn(leftCol.getADQLName(), (byte)0, leftList, true);
						// ...add the left column:
						mapDuplicated.put(leftCol.getADQLName().toLowerCase(), leftCol);
					}
				}

			}
			// CASE: USING
			else if (join.hasJoinedColumns()){
				// For each columns of usingList, check there is in each list exactly one matching column, and then, add it in mapDuplicated
				DBColumn leftCol;
				ADQLColumn usingCol;
				Iterator<ADQLColumn> itCols = join.getJoinedColumns();
				while(itCols.hasNext()){
					usingCol = itCols.next();
					// search for exactly one column with the same name in the LEFT list
					// and throw an exception if there is none, or if there are several matches:
					leftCol = findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), leftList, true);
					// idem in the RIGHT list:
					findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), rightList, false);
					// add the left column:
					mapDuplicated.put((usingCol.isCaseSensitive(IdentifierField.COLUMN) ? ("\"" + usingCol.getColumnName() + "\"") : usingCol.getColumnName().toLowerCase()), leftCol);
				}

			}
			// CASE: NO DUPLICATION TO FIGURE OUT
			else{
				// Return the union of both lists:
				list.addAll(leftList);
				list.addAll(rightList);
				return list;
			}

			/* 2. Add all columns of the left list except the ones identified as duplications */
			addAllExcept2(leftList, list, mapDuplicated);

			/* 3. Add all columns of the right list except the ones identified as duplications */
			addAllExcept2(rightList, list, mapDuplicated);

			/* 4. Add all common columns of mapDuplicated */
			list.addAll(0, mapDuplicated.values());

			return list;
		}catch(UnresolvedJoinException uje){
			uje.setPosition(join.getPosition());
			throw uje;
		}
	}
	
	public final static void addAllExcept2(final SearchColumnList itemsToAdd, final SearchColumnList target, final Map<String,DBColumn> exception){
		for(DBColumn col : itemsToAdd){
			if (!exception.containsKey(col.getADQLName().toLowerCase()) && !exception.containsKey("\"" + col.getADQLName() + "\""))
				target.add(col);
		}
	}

}
+122 −0
Original line number Diff line number Diff line
package adql.query.from;

/*
 * 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 - Astronomisches Rechen Institut (ARI)
 */

import java.util.Collection;

import adql.db.DBColumn;
import adql.db.DBCommonColumn;
import adql.db.SearchColumnList;
import adql.db.exception.UnresolvedJoinException;
import adql.parser.SQLServer_ADQLQueryFactory;
import adql.query.ClauseConstraints;
import adql.query.operand.ADQLColumn;

/**
 * <p>Special implementation of {@link OuterJoin} for MS SQL Server.</p>
 * 
 * <p><b>Important:</b>
 * 	Instances of this class are created only by {@link SQLServer_ADQLQueryFactory}.
 * </p>
 * 
 * <p>
 * 	This implementation just changes the behavior the {@link #getDBColumns()}.
 * 	In MS SQL Server, there is no keyword NATURAL and USING. That's why the {@link DBColumn}s
 * 	returned by {@link DBColumn} can not contain any {@link DBCommonColumn}. Instead,
 * 	the {@link DBColumn} of the first joined table (i.e. the left one) is returned.
 * </p>
 * 
 * <p>
 * 	Since this special behavior is also valid for {@link InnerJoin}, a special implementation
 * 	of this class has been also created: {@link SQLServer_InnerJoin}. Both must have exactly the
 * 	same behavior when {@link #getDBColumns()} is called. That's why the static function
 * 	{@link InnerJoin#getDBColumns(ADQLJoin)} has been created. It is called by {@link SQLServer_InnerJoin}
 * 	and {@link SQLServer_OuterJoin}.
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (ARI)
 * @version 1.4 (03/2016)
 * @since 1.4
 * 
 * @see SQLServer_ADQLQueryFactory
 * @see SQLServer_InnerJoin
 */
public class SQLServer_OuterJoin extends OuterJoin {

	/**
	 * Builds a NATURAL OUTER join between the two given "tables".
	 * 
	 * @param left	Left "table".
	 * @param right	Right "table".
	 * @param type	OUTER join type (left, right or full).
	 * 
	 * @see OuterJoin#OuterJoin(FromContent, FromContent, OuterType)
	 */
	public SQLServer_OuterJoin(FromContent left, FromContent right, OuterType type) {
		super(left, right, type);
	}

	/**
	 * Builds an OUTER join between the two given "tables" with the given condition.
	 * 
	 * @param left		Left "table".
	 * @param right		Right "table".
	 * @param type		Outer join type (left, right or full).
	 * @param condition	Join condition.
	 * 
	 * @see OuterJoin#OuterJoin(FromContent, FromContent, OuterType, ClauseConstraints)
	 */
	public SQLServer_OuterJoin(FromContent left, FromContent right, OuterType type, ClauseConstraints condition) {
		super(left, right, type, condition);
	}

	/**
	 * Builds an OUTER join between the two given "tables" with a list of columns to join.
	 * 
	 * @param left			Left "table".
	 * @param right			Right "table".
	 * @param type			Outer join type.
	 * @param lstColumns	List of columns to join.
	 * 
	 * @see OuterJoin#OuterJoin(FromContent, FromContent, OuterType, Collection)
	 */
	public SQLServer_OuterJoin(FromContent left, FromContent right, OuterType type, Collection<ADQLColumn> lstColumns) {
		super(left, right, type, lstColumns);
	}

	/**
	 * Builds a copy of the given OUTER join.
	 * 
	 * @param toCopy		The OUTER join to copy.
	 * 
	 * @throws Exception	If there is an error during the copy.
	 * 
	 * @see OuterJoin#OuterJoin(OuterJoin)
	 */
	public SQLServer_OuterJoin(OuterJoin toCopy) throws Exception {
		super(toCopy);
	}

	@Override
	public SearchColumnList getDBColumns() throws UnresolvedJoinException{
		return SQLServer_InnerJoin.getDBColumns(this);
	}

}
+398 −0

File added.

Preview size limit exceeded, changes collapsed.

+159 −0
Original line number Diff line number Diff line
package adql.query.from;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import adql.db.DBColumn;
import adql.db.DBCommonColumn;
import adql.db.DBType;
import adql.db.DBType.DBDatatype;
import adql.db.DefaultDBColumn;
import adql.db.DefaultDBTable;
import adql.db.SearchColumnList;
import adql.query.IdentifierField;
import adql.query.operand.ADQLColumn;

public class TestSQLServer_InnerJoin {

	private ADQLTable tableA, tableB, tableC;

	@Before
	public void setUp() throws Exception{
		/* SET THE TABLES AND COLUMNS NEEDED FOR THE TEST */
		// Describe the available table:
		DefaultDBTable metaTableA = new DefaultDBTable("A");
		metaTableA.setADQLSchemaName("public");
		DefaultDBTable metaTableB = new DefaultDBTable("B");
		metaTableB.setADQLSchemaName("public");
		DefaultDBTable metaTableC = new DefaultDBTable("C");
		metaTableC.setADQLSchemaName("public");

		// Describe its columns:
		metaTableA.addColumn(new DefaultDBColumn("id", new DBType(DBDatatype.VARCHAR), metaTableA));
		metaTableA.addColumn(new DefaultDBColumn("txta", new DBType(DBDatatype.VARCHAR), metaTableA));
		metaTableB.addColumn(new DefaultDBColumn("id", new DBType(DBDatatype.VARCHAR), metaTableB));
		metaTableB.addColumn(new DefaultDBColumn("txtb", new DBType(DBDatatype.VARCHAR), metaTableB));
		metaTableC.addColumn(new DefaultDBColumn("Id", new DBType(DBDatatype.VARCHAR), metaTableC));
		metaTableC.addColumn(new DefaultDBColumn("txta", new DBType(DBDatatype.VARCHAR), metaTableC));
		metaTableC.addColumn(new DefaultDBColumn("txtc", new DBType(DBDatatype.VARCHAR), metaTableC));

		// Build the ADQL tables:
		tableA = new ADQLTable("A");
		tableA.setDBLink(metaTableA);
		tableB = new ADQLTable("B");
		tableB.setDBLink(metaTableB);
		tableC = new ADQLTable("C");
		tableC.setDBLink(metaTableC);
	}

	@Test
	public void testGetDBColumns(){
		// Test NATURAL JOIN 1:
		try{
			ADQLJoin join = new SQLServer_InnerJoin(tableA, tableB);
			SearchColumnList joinColumns = join.getDBColumns();
			assertEquals(3, joinColumns.size());
			List<DBColumn> lstFound = joinColumns.search(null, null, null, "id", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
			assertEquals(1, joinColumns.search(null, "public", "A", "id", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "C", "id", IdentifierField.getFullCaseSensitive(true)).size());
			lstFound = joinColumns.search(null, "public", "A", "txta", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			lstFound = joinColumns.search(null, "public", "B", "txtb", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
		}catch(Exception ex){
			ex.printStackTrace();
			fail("This test should have succeeded!");
		}

		// Test NATURAL JOIN 2:
		try{
			ADQLJoin join = new SQLServer_InnerJoin(tableA, tableC);
			SearchColumnList joinColumns = join.getDBColumns();
			assertEquals(3, joinColumns.size());

			// check id (only the column of table A should be found):
			List<DBColumn> lstFound = joinColumns.search(null, null, null, "id", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
			assertEquals(1, joinColumns.search(null, "public", "A", "id", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "C", "id", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());

			// check txta (only the column of table A should be found):
			lstFound = joinColumns.search(null, null, null, "txta", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
			assertEquals(1, joinColumns.search(null, "public", "A", "txta", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "C", "txta", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());

			// check txtc (only for table C)
			lstFound = joinColumns.search(null, null, null, "txtc", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			assertNotNull(lstFound.get(0).getTable());
			assertEquals("C", lstFound.get(0).getTable().getADQLName());
			assertEquals("public", lstFound.get(0).getTable().getADQLSchemaName());

		}catch(Exception ex){
			ex.printStackTrace();
			fail("This test should have succeeded!");
		}

		// Test with a USING("id"):
		try{
			List<ADQLColumn> usingList = new ArrayList<ADQLColumn>(1);
			usingList.add(new ADQLColumn("id"));
			ADQLJoin join = new SQLServer_InnerJoin(tableA, tableC, usingList);
			SearchColumnList joinColumns = join.getDBColumns();
			assertEquals(4, joinColumns.size());

			// check id (only the column of table A should be found):
			List<DBColumn> lstFound = joinColumns.search(null, null, null, "id", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
			assertEquals(1, joinColumns.search(null, "public", "A", "id", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "C", "id", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());

			// check A.txta and C.txta:
			lstFound = joinColumns.search(null, null, null, "txta", IdentifierField.getFullCaseSensitive(true));
			assertEquals(2, lstFound.size());
			// A.txta
			assertNotNull(lstFound.get(0).getTable());
			assertEquals("A", lstFound.get(0).getTable().getADQLName());
			assertEquals("public", lstFound.get(0).getTable().getADQLSchemaName());
			assertEquals(1, joinColumns.search(null, "public", "A", "txta", IdentifierField.getFullCaseSensitive(true)).size());
			// C.txta
			assertNotNull(lstFound.get(1).getTable());
			assertEquals("C", lstFound.get(1).getTable().getADQLName());
			assertEquals("public", lstFound.get(1).getTable().getADQLSchemaName());
			assertEquals(1, joinColumns.search(null, "public", "C", "txta", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "B", "txta", IdentifierField.getFullCaseSensitive(true)).size());

			// check txtc (only for table C):
			lstFound = joinColumns.search(null, null, null, "txtc", IdentifierField.getFullCaseSensitive(true));
			assertEquals(1, lstFound.size());
			assertNotNull(lstFound.get(0).getTable());
			assertEquals("C", lstFound.get(0).getTable().getADQLName());
			assertEquals("public", lstFound.get(0).getTable().getADQLSchemaName());
			assertEquals(1, joinColumns.search(null, "public", "C", "txtc", IdentifierField.getFullCaseSensitive(true)).size());
			assertEquals(0, joinColumns.search(null, "public", "A", "txtc", IdentifierField.getFullCaseSensitive(true)).size());

		}catch(Exception ex){
			ex.printStackTrace();
			fail("This test should have succeeded!");
		}
	}

}
Loading