/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package org.teiid.olingo;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;

import javax.servlet.DispatcherType;

import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.core.Encoder;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.infinispan.transaction.tm.DummyBaseTransactionManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.teiid.GeneratedKeys;
import org.teiid.adminapi.Admin.SchemaObjectType;
import org.teiid.adminapi.Model;
import org.teiid.adminapi.impl.ModelMetaData;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.util.ObjectConverterUtil;
import org.teiid.core.util.TimestampWithTimezone;
import org.teiid.core.util.UnitTestUtil;
import org.teiid.deployers.VirtualDatabaseException;
import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository.ConnectorManagerException;
import org.teiid.dqp.service.AutoGenDataService;
import org.teiid.jdbc.ConnectionImpl;
import org.teiid.jdbc.TeiidDriver;
import org.teiid.language.QueryExpression;
import org.teiid.metadata.KeyRecord;
import org.teiid.metadata.MetadataStore;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.Schema;
import org.teiid.metadata.Table;
import org.teiid.odata.api.Client;
import org.teiid.odata.api.SQLParameter;
import org.teiid.odata.api.UpdateResponse;
import org.teiid.olingo.service.LocalClient;
import org.teiid.olingo.web.ODataFilter;
import org.teiid.olingo.web.ODataServlet;
import org.teiid.query.metadata.DDLStringVisitor;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.unittest.RealMetadataFactory;
import org.teiid.query.unittest.TimestampUtil;
import org.teiid.runtime.EmbeddedConfiguration;
import org.teiid.runtime.EmbeddedServer;
import org.teiid.runtime.HardCodedExecutionFactory;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.ResultSetExecution;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.UpdateExecution;
import org.teiid.translator.loopback.LoopbackExecutionFactory;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@SuppressWarnings("nls")
public class TestODataIntegration {
    private static final class UnitTestLocalClient extends LocalClient {
		private final Properties properties;
		private final TeiidDriver driver;
		private final String vdb;
		ConnectionImpl conn;
		private boolean throwUpdateException;
		private boolean rollback;

		private UnitTestLocalClient(String vdbName, Integer vdbVersion,
				Properties properties, Properties properties2,
				TeiidDriver driver, String vdb) {
			super(vdbName, vdbVersion, properties);
			this.properties = properties2;
			this.driver = driver;
			this.vdb = vdb;
		}

		@Override
		public ConnectionImpl getConnection() {
		    return conn;
		}

		@Override
		public void close() throws SQLException {
		    if (conn != null) {
		        conn.close();
		    }
		}

		@Override
		public Connection open() throws SQLException {
		    try {
		        conn = LocalClient.buildConnection(driver, vdb, 1, properties);
		        return conn;
		    } catch (SQLException e) {
		        e.printStackTrace();
		    }
		    return null;
		}

		@Override
		public UpdateResponse executeUpdate(Command query,
				List<SQLParameter> parameters) throws SQLException {
			if (throwUpdateException) {
				throw new TeiidRuntimeException();
			}
			return super.executeUpdate(query, parameters);
		}
		
		public void setThrowUpdateException(boolean throwUpdateException) {
			this.throwUpdateException = throwUpdateException;
		}
		
		@Override
		public void rollback(String txnId) throws SQLException {
			rollback = true;
			super.rollback(txnId);
		}
		
		public boolean isRollback() {
			return rollback;
		}
	}

	private static final String CRLF = "\r\n";
      private static final String MIME_HEADERS = "Content-Type: application/http" + CRLF
              + "Content-Transfer-Encoding: binary" + CRLF;
    private static EmbeddedServer teiid;
    private static Server server = new Server();
    private static String baseURL;
    private static HttpClient http = new HttpClient();
    private static UnitTestLocalClient localClient;

    @Before
    public void before() throws Exception {
    	TimestampWithTimezone.resetCalendar(TimeZone.getTimeZone("UTC"));
        teiid = new EmbeddedServer();
        EmbeddedConfiguration config = new EmbeddedConfiguration();
        config.setTransactionManager(new DummyBaseTransactionManager());
        teiid.start(config);
        teiid.addTranslator("loopback", new LoopbackExecutionFactory() {
        	@Override
        	public boolean supportsRowOffset() {
        		return false;
        	}
        });

        createContext("/odata4", null);
        
        deployVDB();
    }

	private void createContext(String contextPath, Map<String, String> properties) throws Exception {
		ServerConnector connector = new ServerConnector(server);
        server.setConnectors(new Connector[] { connector });

        ServletContextHandler context = new ServletContextHandler();
        if (properties != null) {
        	for (Map.Entry<String, String> prop : properties.entrySet()) {
        		context.setInitParameter(prop.getKey(), prop.getValue());
        	}
        }
        context.setContextPath(contextPath);
        context.addServlet(new ServletHolder(new ODataServlet()), "/*");
        context.addFilter(new FilterHolder(new ODataFilter() {
            @Override
            public Client buildClient(String vdbName, int version, Properties props) {
                if (localClient != null) {
                    return localClient;
                }
                return getClient(teiid.getDriver(), vdbName, version, props);       
            }
        }), "/*", EnumSet.allOf(DispatcherType.class));
        server.setHandler(context);
        server.start();
        int port = connector.getLocalPort();
        http.start();
        baseURL = "http://localhost:"+port+contextPath;        
    }

    @After
    public void after() throws Exception {
    	http.stop();
    	TimestampWithTimezone.resetCalendar(null);
        server.stop();
        teiid.stop();
    }

    private static void deployVDB() throws IOException,
            ConnectorManagerException, VirtualDatabaseException,
            TranslatorException {
        teiid.deployVDB(new FileInputStream(UnitTestUtil.getTestDataFile("loopy-vdb.xml")));
    }

    private JsonNode getJSONNode(ContentResponse response) throws IOException,
            JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode node = objectMapper.readTree(response.getContent());
        return node;
    }    
    
    @Test
    public void testMetadata() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/$metadata");
        assertEquals(200, response.getStatus());
        assertEquals(ObjectConverterUtil.convertFileToString(
                UnitTestUtil.getTestDataFile("loopy-edmx-metadata.xml")).replace("${baseurl}", baseURL),
                response.getContentAsString());        
    }
    @Test
    public void testSystemMetadata() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/SYS/$metadata");
        assertEquals(200, response.getStatus());
    }

    @Test
    public void testServiceMetadata() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/VM1");
        assertEquals(200, response.getStatus());
        String expected = "{" +  
                "\"@odata.context\":\""+baseURL+"/loopy/VM1/$metadata\"," +
                "\"value\":[{" +
                "\"name\":\"G1\"," +
                "\"url\":\"G1\"" +
                "},{" +
                "\"name\":\"G2\"," +
                "\"url\":\"G2\"" +
                "},{" +
                "\"name\":\"G4\"," +
                "\"url\":\"G4\"" +
                "},{\"name\":\"LobTable\",\"url\":\"LobTable\"}]" +
                "}";
        assertEquals(expected, response.getContentAsString());
    }
    
    @Test
    public void testFilterExpression() throws Exception {
    	//won't resolve
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1?$filter=e1");
        assertEquals(400, response.getStatus());
        
        response = http.GET(baseURL + "/loopy/vm1/G1?$filter=true");
        assertEquals(200, response.getStatus());
    }

    @Test
    public void testEntitySet() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G1\",\"value\":[{\"e1\":\"ABCDEFGHIJ\",\"e2\":0,\"e3\":0.0}]}", 
                response.getContentAsString());
    }
    
    @Test
    public void testEntitySetSkipOnly() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1?$skip=1");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G1\",\"value\":[]}", 
                response.getContentAsString());
    }

    @Test
    public void testEntitySetWithKey() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1(0)");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G1/$entity\",\"e1\":\"ABCDEFGHIJ\",\"e2\":0,\"e3\":0.0}", 
                response.getContentAsString());        
    }

    @Test
    public void testIndividualProperty() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1(0)/e1");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G1(0)/e1\",\"value\":\"ABCDEFGHIJ\"}", 
                response.getContentAsString());        
    }
    
    @Test
    public void testEntity() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1(0)");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G1/$entity\",\"e1\":\"ABCDEFGHIJ\",\"e2\":0,\"e3\":0.0}", 
                response.getContentAsString());        
    }    
    
    @Test
    public void testIndividualProperty$Value() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1(0)/e1/$value");
        assertEquals(200, response.getStatus());
        assertEquals("ABCDEFGHIJ",response.getContentAsString());        
    }
    
    @Test
    public void testNavigation_1_to_1() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/pm1/G2(0)/FK0");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G1/$entity\",\"e1\":\"ABCDEFGHIJ\",\"e2\":0,\"e3\":0.0}", 
                response.getContentAsString());
    }
    
    @Test
    public void testNavigation_1_to_many() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/G1(0)/G2_FK0");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#G2\",\"value\":[{\"e1\":\"ABCDEFGHIJ\",\"e2\":0}]}", 
                response.getContentAsString());
    }     
    
    @Test
    public void testInsert() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("INSERT INTO x (a, b) VALUES ('teiid', 'dv')", new int[] {1});
        teiid.addTranslator("x10", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);");
            mmd.addSourceMapping("x10", "x10", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());
            
            String payload = "{\n" +
                    "  \"a\":\"teiid\",\n" +
                    "  \"b\":\"dv\"\n" +
                    "}";
            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x")
                .method("POST")
                .content(new StringContentProvider(payload))
                .header("Content-Type", "application/json")
                .header("Prefer", "return=minimal")
                .send();
            assertEquals(204, response.getStatus());
            assertTrue(response.getHeaders().get("OData-EntityId").endsWith("northwind/m/x('ABCDEFG')"));
            //assertEquals("ABCDEFGHIJ",response.getContentAsString()); 
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    @Test
    public void testInsertDifferentTypes() throws Exception {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory() {
            @Override
            public boolean supportsCompareCriteriaEquals() {
                return true;
            }

            @Override
            public UpdateExecution createUpdateExecution(
                    org.teiid.language.Command command,
                    ExecutionContext executionContext,
                    RuntimeMetadata metadata, Object connection)
                    throws TranslatorException {
                addUpdate(command.toString(), new int[] {1});
                return super.createUpdateExecution(command, executionContext, metadata,connection);
            }
           
        };
        
        hc.addUpdate("INSERT INTO PostTable (intkey, intnum, stringkey, stringval, booleanval, "
                + "decimalval, timeval, dateval, timestampval) "
                + "VALUES (4, 4, '4', 'value_4', FALSE, -20.4, {t '00:00:04'}, "
                + "{d '2004-04-04'}, {ts '2004-01-01 00:00:04.0'})", 
                new int[] {1});
        hc.addData("SELECT PostTable.intkey, PostTable.intnum, PostTable.stringkey, "
                + "PostTable.stringval, PostTable.booleanval, PostTable.decimalval, "
                + "PostTable.timeval, PostTable.dateval, PostTable.timestampval, "
                + "PostTable.clobval FROM PostTable "
                + "WHERE PostTable.intkey = 4", 
                Arrays.asList(Arrays.asList(4, 4, "4", "value_4", false, new BigDecimal("-20.4"), 
                        new java.sql.Time(0), new java.sql.Date(0), 
                        new java.sql.Timestamp(0), null)));
        teiid.addTranslator("x11", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "CREATE foreign TABLE PostTable(\n"  
                    + "intkey integer PRIMARY KEY,\n"
                    + "intnum integer,\n"
                    + "stringkey varchar(20),\n"
                    + "stringval varchar(20),\n"
                    + "booleanval boolean,\n" + 
                    "  decimalval decimal(20, 10),\n"
                    + "timeval time,\n"
                    + "dateval date,\n"
                    + "timestampval timestamp,\n"
                    + "clobval clob) options (updatable true);");
            mmd.addSourceMapping("x11", "x11", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());
            String payload = "\n" +  
                    "<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:d=\"http://docs.oasis-open.org/odata/ns/data\" "
                    + "xmlns:georss=\"http://www.georss.org/georss\" "
                    + "xmlns:gml=\"http://www.opengis.net/gml\" "
                    + "xmlns:m=\"http://docs.oasis-open.org/odata/ns/metadata\">\n" + 
                    "   <category scheme=\"http://docs.oasis-open.org/odata/ns/scheme\" />\n" + 
                    "   <content type=\"application/xml\">\n" + 
                    "      <m:properties>\n" + 
                    "         <d:intkey m:type=\"Int32\">4</d:intkey>\n" + 
                    "         <d:intnum m:type=\"Int32\">4</d:intnum>\n" + 
                    "         <d:stringkey>4</d:stringkey>\n" + 
                    "         <d:stringval>value_4</d:stringval>\n" + 
                    "         <d:booleanval m:type=\"Boolean\">false</d:booleanval>\n" + 
                    "         <d:decimalval m:type=\"Double\">-20.4</d:decimalval>\n" + 
                    "         <d:timeval m:type=\"TimeOfDay\">00:00:04</d:timeval>\n" + 
                    "         <d:dateval m:type=\"Date\">2004-04-04</d:dateval>\n" + 
                    "         <d:timestampval m:type=\"DateTimeOffset\">2004-01-01T00:00:04Z</d:timestampval>\n" + 
                    "      </m:properties>\n" + 
                    "   </content>\n" + 
                    "</entry>";
            ContentResponse response = http.newRequest(baseURL + "/northwind/m/PostTable")
                .method("POST")
                .content(new StringContentProvider(payload))
                .header("Content-Type", "application/xml")
                .header("Prefer", "return=minimal")
                .send();
            assertEquals(204, response.getStatus());
            assertTrue(response.getHeaders().get("OData-EntityId"), 
                    response.getHeaders().get("OData-EntityId").endsWith("northwind/m/PostTable(4)"));
            
            String jsonPlayload= "{\n" +
                    "  \"intkey\":4,\n" +
                    "  \"intnum\":4,\n" +
                    "  \"stringkey\":\"4\",\n" +
                    "  \"stringval\":\"value_4\",\n" +
                    "  \"booleanval\":false,\n" +
                    "  \"decimalval\":-20.4,\n" +
                    "  \"timeval\":\"00:00:04\",\n" +
                    "  \"dateval\":\"2004-04-04\",\n" +
                    "  \"timestampval\":\"2004-01-01T00:00:04Z\"" +
                    "}";
            response = http.newRequest(baseURL + "/northwind/m/PostTable")
                    .method("POST")
                    .content(new StringContentProvider(jsonPlayload))
                    .header("Content-Type", "application/json")
                    .header("Prefer", "return=representation")
                    .send();
                assertEquals(201, response.getStatus());
                assertEquals("{\"@odata.context\":\"$metadata#PostTable\","
                        + "\"intkey\":4,"
                        + "\"intnum\":4,"
                        + "\"stringkey\":\"4\","
                        + "\"stringval\":\"value_4\","
                        + "\"booleanval\":false,"
                        + "\"decimalval\":-20.4,"
                        + "\"timeval\":\"00:00:00\","
                        + "\"dateval\":\"1970-01-01\","
                        + "\"timestampval\":\"1970-01-01T00:00:00Z\","
                        + "\"clobval\":null}", response.getContentAsString());
                
                response = http.newRequest(baseURL + "/northwind/m/PostTable(4)/clobval")
                        .method("POST")
                        .content(new StringContentProvider("clob value"))
                        .send();
                assertEquals(405, response.getStatus());
                
                response = http.newRequest(baseURL + "/northwind/m/PostTable(4)/clobval")
                        .method("PUT")
                        .content(new StringContentProvider("clob value"))
                        .send();
                assertEquals(204, response.getStatus());
                
                response = http.newRequest(baseURL + "/northwind/m/PostTable(4)/clobval")
                        .method("DELETE")
                        .send();
                assertEquals(204, response.getStatus());                
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }     
    
    @Test 
    public void testDeepInsert() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("INSERT INTO x (a, b) VALUES ('teiid', 'dv')", new int[] {1});
        hc.addUpdate("INSERT INTO y (a, b) VALUES ('odata', 'olingo')", new int[] {1});
        hc.addUpdate("INSERT INTO y (a, b) VALUES ('odata4', 'olingo4')", new int[] {1});
        teiid.addTranslator("x10", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);"
                    + "create foreign table y ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (b) REFERENCES x(a)"                    
                    + ") options (updatable true);");
            mmd.addSourceMapping("x10", "x10", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());
            
            // update to collection based reference
            String payload = "{\n" +
                    "  \"a\":\"teiid\",\n" +
                    "  \"b\":\"dv\",\n" +
                    "     \"y_FKX\": [\n"+
                    "        {"+
                    "          \"a\":\"odata\",\n" +
                    "          \"b\":\"olingo\"\n" +                    
                    "        },\n"+
                    "        {\n"+
                    "          \"a\":\"odata4\",\n" +
                    "          \"b\":\"olingo4\"\n" +                    
                    "        }\n"+                    
                    "     ]\n"+
                    "}";
            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x")
                    .method("POST")
                    .content(new StringContentProvider(payload), ContentType.APPLICATION_JSON.toString())
                    // when this header is defined the return should be expanded, but due to way olingo 
                    // designed it is going to be a big refactoring.
                    .header("Prefer", "return=representation")
                    .send();
            assertEquals(201, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"}", 
                    response.getContentAsString());

        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }     

    @Test
    public void testFunction() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/proc(x='foo')");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Edm.String\",\"value\":\"foo\"}", 
                response.getContentAsString());        
    }
    
    @Test
    public void testFunctionDate() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/getCustomers(p2=2011-09-11T00:00:00Z,p3=2.0)");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Edm.DateTimeOffset\",\"value\":\"2011-09-11T00:00:00Z\"}", 
                response.getContentAsString());        
    }    
    
    @Test
    public void testFunctionReturningResultSet() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/procResultSet(x='foo''bar',y=1)");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Collection(Loopy.1.VM1.procResultSet_RSParam)\","
                + "\"value\":[{\"x\":\"foo'bar\",\"y\":1},"
                + "{\"x\":\"second\",\"y\":2},"
                + "{\"x\":\"third\",\"y\":3}]}", 
                response.getContentAsString());
        
        response = http.GET(baseURL + "/loopy/vm1/procResultSet(x='foo',y='1'");
        assertEquals(400, response.getStatus());
        
        response = http.GET(baseURL + "/loopy/vm1/procResultSet(x='foo''bar',y=1)?$filter="+Encoder.encode("y eq 3"));
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Collection(Loopy.1.VM1.procResultSet_RSParam)\","
                + "\"value\":[{\"x\":\"third\",\"y\":3}]}", response.getContentAsString());        

        response = http.GET(baseURL + "/loopy/vm1/procResultSet(x='foo''bar',y=1)?$skip=1&$top=1");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Collection(Loopy.1.VM1.procResultSet_RSParam)\","
                + "\"value\":[{\"x\":\"second\",\"y\":2}]}", response.getContentAsString());        

        response = http.GET(baseURL + "/loopy/vm1/procResultSet(x='foo''bar',y=1)?$orderby=y%20desc");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Collection(Loopy.1.VM1.procResultSet_RSParam)\","
                + "\"value\":[{\"x\":\"third\",\"y\":3},"
                + "{\"x\":\"second\",\"y\":2},"
                + "{\"x\":\"foo'bar\",\"y\":1}]}", 
                response.getContentAsString());                
    }
    
    @Test
    public void testFunctionReturningStream() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/procXML(x='foo')");
        assertEquals(200, response.getStatus());
        assertEquals("<name>foo</name>", 
                response.getContentAsString());        
    }
    
    @Test
    public void testFunctionReturningStreamDesignedToReturnTable() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/procComposableXML(x='foo')");
        //can't handle multiple lob rows
        assertEquals(404, response.getStatus());
    }
    
    @Test
    public void testFunctionReturningReturningArray() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/getCustomerIds(p1=1)");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Collection(Edm.Int32)\",\"value\":[1,2]}", 
                response.getContentAsString());
    }
    
    @Test
    public void testActionStream() throws Exception {
        ContentResponse response = http.newRequest(baseURL + "/loopy/vm1/actionXML")
                .method("POST")
                .content(new StringContentProvider("<name>foo2</name>"), "application/xml")
                .send();
        assertEquals(200, response.getStatus());
        assertEquals("<name>foo2</name>", 
                response.getContentAsString());        
    }
    
    @Test
    public void testAllowHeaderOnMethodNotSupported() throws Exception {
        ContentResponse response = http.newRequest(baseURL + "/loopy/vm1/actionXML")
                .method("GET")
                .content(new StringContentProvider("<name>foo2</name>"), "application/xml")
                .send();
        assertEquals(405, response.getStatus());
        assertEquals("POST", getHeader(response, "Allow"));
    }    
    
    @Test
    public void testActionSimpleParameters() throws Exception {
        ContentResponse response = http.newRequest(baseURL + "/loopy/vm1/procActionJSON")
                .method("POST")
                .content(new StringContentProvider("{\"x\": \"foo\", \"y\": 4.5}"), "application/json")
                .send();
        assertEquals(200, response.getStatus());
        assertEquals("{\"x1\":\"foo\",\"y1\":4.5}", 
                response.getContentAsString());        
    }     

    @Test
    public void testMetadataVisibility() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/PM2/G1");
        assertEquals(500, response.getStatus());
    }
        
    @Test 
    public void testCheckGeneratedColumns() throws Exception {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory() {
            @Override
            public UpdateExecution createUpdateExecution(
                    org.teiid.language.Command command,
                    ExecutionContext executionContext,
                    RuntimeMetadata metadata, Object connection)
                    throws TranslatorException {
                GeneratedKeys keys = executionContext.getCommandContext().returnGeneratedKeys(
                        new String[] { "a" },new Class[] { String.class });
                keys.addKey(Arrays.asList("ax"));
                return super.createUpdateExecution(command, executionContext, metadata,
                        connection);
            }
            @Override
            public boolean supportsCompareCriteriaEquals() {
                return true;
            }
        };
        hc.addUpdate("INSERT INTO x (b, c) VALUES ('b', 5)", new int[] {1});
        // this gets called right after insert.
        hc.addData("SELECT x.a, x.b, x.c FROM x WHERE x.a = 'ax'", Arrays.asList(Arrays.asList("a", "b", 2)));
        teiid.addTranslator("x", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b string, c integer, "
                    + "primary key (a)) options (updatable true);");
            mmd.addSourceMapping("x", "x", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x")
                    .method("POST")
                    .content(new StringContentProvider("{\"b\":\"b\", \"c\":5}"), "application/json")
                    .send();                        
            assertEquals(201, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    private UnitTestLocalClient getClient(final TeiidDriver driver, final String vdb, final int version, final Properties properties) {
        return new UnitTestLocalClient(vdb, 1, properties, properties, driver, vdb);
    }
    
    @Test
    public void testSkipNoPKTable() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/PM1/NoPKTable");
        assertEquals(404, response.getStatus());
        assertEquals("{\"error\":{\"code\":null,\"message\":\"Cannot find EntitySet, Singleton, "
                + "ActionImport or FunctionImport with name 'NoPKTable'.\"}}", 
                response.getContentAsString());
    }
    
    @Test 
    public void testInvalidCharacterReplacement() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("DDL", "create view x (a string primary key, b char, "
                    + "c string[], d integer) as select 'ab\u0000cd\u0001', char(22), "
                    + "('a\u00021','b1'), 1;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            props.setProperty(LocalClient.INVALID_CHARACTER_REPLACEMENT, " ");
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            ContentResponse response = null;
            response = http.newRequest(baseURL + "/northwind/vw/x")
                    .method("GET")
                    .header("Accept", "application/xml")
                    .send(); 
            
            assertEquals(200, response.getStatus());
            String payload = 
                    "<a:content type=\"application/xml\">"
                        + "<m:properties><d:a>ab cd </d:a>"
                            + "<d:b> </d:b>"
                            + "<d:c m:type=\"#Collection(String)\">"
                                + "<m:element>a 1</m:element>"
                                + "<m:element>b1</m:element>"
                            + "</d:c>"
                            + "<d:d m:type=\"Int32\">1</d:d>"
                        + "</m:properties>"
                    + "</a:content>";
            
            assertTrue(response.getContentAsString().contains(payload));

            response = http.GET(baseURL + "/northwind/vw/x");
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":["
                    + "{\"a\":\"ab\\u0000cd\\u0001\",\"b\":\"\\u0016\",\"c\":[\"a\\u00021\",\"b1\"],\"d\":1}]}", 
                    response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testArrayResults() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("DDL", "create view x (a string primary key, b integer[], c string[][]) "
                    + "as select 'x', (1, 2, 3), (('a','b'),('c','d')) union "
                    + "select 'y', (4, 5, 6), (('x','y'),('z','u'));");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$format=json&$select=a,b");
            assertEquals(200, response.getStatus());
            assertEquals(
                    "{\"@odata.context\":\"$metadata#x(a,b)\","
                    + "\"value\":[{\"a\":\"x\",\"b\":[1,2,3]},{\"a\":\"y\",\"b\":[4,5,6]}]}",
                    response.getContentAsString());

            // since there are multi-dimentional array it will be not supported
            response = http.GET(baseURL + "/northwind/vw/x?$format=json");
            assertEquals(501, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @SuppressWarnings("unchecked")
    @Test 
    public void test$ItFilter() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addData("SELECT x.c, x.a FROM x WHERE x.a = 'x'", Arrays.asList(Arrays.asList(new String[] {"google.net", "google.com"}, 'x')));
        hc.addData("SELECT x.c, x.a FROM x WHERE x.a = 'y'", Arrays.asList(Arrays.asList(new String[] {"example.net", "example.com"}, 'y')));
        
        teiid.addTranslator("x8", hc);
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("DDL", "create foreign table x  (a string primary key, b integer[], c string[]);");
            mmd.setModelType(Model.Type.PHYSICAL);
            mmd.addSourceMapping("x8", "x8", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.GET(baseURL + "/northwind/vw/x('x')/c?$filter=endswith($it,'com')");
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x('x')/c\",\"value\":[\"google.com\"]}", response.getContentAsString());

            response = http.GET(baseURL + "/northwind/vw/x('y')/c?$filter=startswith($it,'example')");
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x('y')/c\",\"value\":[\"example.net\",\"example.com\"]}", response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    @Test 
    public void testArrayInsertResults() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("INSERT INTO x (a, b) VALUES ('x', (1, 2, 3))", new int[] {1});
        teiid.addTranslator("x5", hc);

        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("DDL", "create foreign table x (a string primary key, b integer[], c string[][]) OPTIONS (updatable true);");
            mmd.setModelType(Model.Type.PHYSICAL);
            mmd.addSourceMapping("x5", "x5", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x")
                    .method("POST")
                    .content(new StringContentProvider("{\"a\":\"x\",\"b\":[1,2,3]}"), "application/json")
                    .send();                        
            assertEquals(201, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testArrayUpdateResults() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("UPDATE x SET b = (1, 2, 3) WHERE x.a = 'x'", new int[] {1});
        teiid.addTranslator("x6", hc);

        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("DDL", "create foreign table x (a string primary key, b integer[], c string[][]) OPTIONS (updatable true);");
            mmd.setModelType(Model.Type.PHYSICAL);
            mmd.addSourceMapping("x6", "x6", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());
            
            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x('x')")
                    .method("PATCH")
                    .content(new StringContentProvider("{\"a\":\"x\",\"b\":[1,2,3]}"), "application/json")
                    .send();                        
            assertEquals(204, response.getStatus());            
        
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    
    @Test 
    public void testSkipToken() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            props.setProperty("batch-size", "1");
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$format=json");
            assertEquals(200, response.getStatus());
            String starts = "{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"abc\",\"b\":456}],"
                    + "\"@odata.nextLink\":\""+baseURL+"/northwind/vw/x?$format=json&$skiptoken=";
            String ends = ",1\"}";
            assertTrue(response.getContentAsString(), response.getContentAsString().startsWith(starts));
            assertTrue(response.getContentAsString(), response.getContentAsString().endsWith(ends));
            
            JsonNode node = getJSONNode(response);
            String nextLink = node.get("@odata.nextLink").asText();
            response = http.GET(nextLink);
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"xyz\",\"b\":123}]}", 
                    response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testSkipTokenNoSystemOptions() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            props.setProperty("batch-size", "1");
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/x");
            assertEquals(200, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void test$SkipWithNegitive() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$skip=-1");
            assertEquals(400, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }     
    
    @Test 
    public void testAlias() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL
                    + "/northwind/vw/x?$filter=" + Encoder.encode("a eq @a")
                    + "&" + Encoder.encode("@a")+"="+Encoder.encode("'xyz'"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"xyz\",\"b\":123}]}", 
                    response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    } 
    
    @Test 
    public void testNegitive$Top() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL+ "/northwind/vw/x?$top=-1");
            assertEquals(400, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }     

    @Test 
    public void testAliasNoValue() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$filter="+Encoder.encode("a eq @a"));
            assertEquals(200, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    } 
    
    @Test 
    public void testSkipTokenWithPageSize() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) as "
                    + "select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            ContentResponse response = http.newRequest(baseURL + "/northwind/vw/x?$format=json")
                .header("Prefer", "odata.maxpagesize=1")
                .send();

            assertEquals(200, response.getStatus());
            String starts = "{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"abc\",\"b\":456}],"
                    + "\"@odata.nextLink\":\""+baseURL+"/northwind/vw/x?$format=json&$skiptoken=";
            String ends = ",1\"}";
            assertTrue(response.getContentAsString(), response.getContentAsString().startsWith(starts));
            assertTrue(response.getContentAsString(), response.getContentAsString().endsWith(ends));
            assertEquals("odata.maxpagesize=1", getHeader(response, "Preference-Applied"));
            
            JsonNode node = getJSONNode(response);
            String nextLink = node.get("@odata.nextLink").asText();
            response = http.GET(nextLink);
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"xyz\",\"b\":123}]}", 
                    response.getContentAsString());
        } finally {
            teiid.undeployVDB("northwind");
        }
    }    

    private String getHeader(ContentResponse response, String header) {
        return response.getHeaders().get(header);
    }
    
    @Test 
    public void testCount() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) "
                    + "as select 'a', 123 "
                    + "union all select 'b', 456 "
                    + "union all select 'c', 789 "
                    + "union all select 'd', 012;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            props.setProperty("batch-size", "1");
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);

            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$format=json&$count=true&$top=1&$skip=1");
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"@odata.count\":4,\"value\":[{\"a\":\"b\",\"b\":456}]}", 
                    response.getContentAsString());
            
            response = http.GET(baseURL + "/northwind/vw/x?$format=json&$count=true&$filter="+Encoder.encode("a eq 'a'"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"@odata.count\":1,\"value\":[{\"a\":\"a\",\"b\":123}]}", 
                    response.getContentAsString());
            
            //effectively the same as above
            response = http.GET(baseURL + "/northwind/vw/x?$format=json&$count=true&$skip=1");
            assertEquals(200, response.getStatus());
            String r = "{\"@odata.context\":\"$metadata#x\",\"@odata.count\":4,"
                    + "\"value\":[{\"a\":\"b\",\"b\":456}],\"@odata.nextLink\":";
            assertTrue(response.getContentAsString(), response.getContentAsString().startsWith(r));
            
            //now there should be a next
            response = http.GET(baseURL + "/northwind/vw/x?$format=json&$count=true");
            assertEquals(200, response.getStatus());
            String ends = ",1,4\"}";
            String responseStr = response.getContentAsString();
            assertTrue(responseStr, responseStr.endsWith(ends));

            //Next string
            JsonParser parser = new JsonFactory(new ObjectMapper()
                    .configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true))
                    .createParser(responseStr);
            JsonNode node = parser.getCodec().readTree(parser);
            
            response = http.newRequest(node.get("@odata.nextLink").asText())
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            responseStr = response.getContentAsString();
            assertTrue(responseStr, responseStr.startsWith("{\"@odata.context\":\"$metadata#x\","
                    + "\"@odata.count\":4,"
                    + "\"value\":[{\"a\":\"b\",\"b\":456}],"
                    + "\"@odata.nextLink\":\""+baseURL+"/northwind/vw/x?$format=json&$count=true&$skiptoken="));
        
            response = http.GET(baseURL + "/northwind/vw/x/$count");
            assertEquals(200, response.getStatus());            
            assertEquals("4", response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }

    @Test 
    public void testCompositeKeyUpdates() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("DELETE FROM x WHERE x.a = 'a' AND x.b = 'b'", new int[] {1});
        hc.addUpdate("INSERT INTO x (a, b, c) VALUES ('a', 'b', 5)", new int[] {1});
        hc.addUpdate("UPDATE x SET c = 10 WHERE x.a = 'a' AND x.b = 'b'", new int[] {1});
        hc.addData("SELECT x.a, x.b, x.c FROM x WHERE x.a = 'a' AND x.b = 'b'", 
                Arrays.asList(Arrays.asList("a", "b", 1)));
        teiid.addTranslator("x1", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b string, c integer, "
                    + "primary key (a, b)) options (updatable true);");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x(a='a',b='b')")
                    .method("DELETE")
                    .send();
            assertEquals(204, response.getStatus());

            //partial key
            response = http.newRequest(baseURL + "/northwind/m/x('a')")
                    .method("DELETE")
                    .send();            
            assertEquals(400, response.getStatus());

            //partial key
            response = http.newRequest(baseURL + "/northwind/m/x(a='a',a='b')")
                    .method("DELETE")
                    .send();                        
            assertEquals(400, response.getStatus());

            //not supported
            //request = new ClientRequest(TestPortProvider.generateURL("/odata/northwind/x(a='a',b='b')/c/$value"));
            //request.body("text/plain", "5");
            //response = request.put(String.class);

            response = http.newRequest(baseURL + "/northwind/m/x")
                    .method("POST")
                    .content(new StringContentProvider("{\"a\":\"a\", \"b\":\"b\", \"c\":5}"), "application/json")
                    .send();                        
            assertEquals(201, response.getStatus());
            
            response = http.newRequest(baseURL + "/northwind/m/x(a='a',b='b')")
                    .method("PATCH")
                    .content(new StringContentProvider("{\"c\":10}"), "application/json")
                    .send();                        
            assertEquals(204, response.getStatus());
            
            response = http.newRequest(baseURL + "/northwind/m/x(a='a',b='b')")
                    .method("PUT")
                    .content(new StringContentProvider("{\"a\":\"a\", \"b\":\"b\", \"c\":5}"), "application/json")
                    .send();                        
            assertEquals(204, response.getStatus());            

        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testPutFailure() throws Exception {
    	HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
    	teiid.addTranslator("x1", hc);
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b string, c integer, "
                    + "primary key (a, b)) options (updatable true);");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());
            localClient.setThrowUpdateException(true);

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x(a='a',b='b')")
                    .method("PUT")
                    .content(new StringContentProvider("{\"a\":\"a\", \"b\":\"b\", \"c\":5}"), "application/json")
                    .send();                        
            assertEquals(500, response.getStatus());            
            assertTrue(localClient.isRollback());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }

    @Test 
    public void testPutRawValue() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory(); 
        hc.addUpdate("UPDATE x SET c = 6 WHERE x.a = 'a'", new int[] {1});
        hc.addUpdate("UPDATE x SET b = '6' WHERE x.a = 'a'", new int[] {1});
        teiid.addTranslator("x1", hc);
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b string, c integer, "
                    + "primary key (a)) options (updatable true);");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x('a')/c/$value")
                    .method("PUT")
                    .content(new BytesContentProvider("6".getBytes()))
                    .send();                        
            assertEquals(204, response.getStatus());
            
            response = http.newRequest(baseURL + "/northwind/m/x('a')/b/$value")
                    .method("PUT")
                    .content(new BytesContentProvider("6".getBytes()))
                    .send();                        
            assertEquals(204, response.getStatus());            
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testEntityId() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory(); 
        hc.addUpdate("UPDATE x SET c = 6 WHERE x.a = 'a'", new int[] {1});
        hc.addUpdate("UPDATE x SET b = '6' WHERE x.a = 'a'", new int[] {1});
        teiid.addTranslator("x1", hc);
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b string, c integer, "
                    + "primary key (a)) options (updatable true);");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/$entity?$id="+baseURL+"/northwind/m/x('a')&$select=b")
                    .method("GET")
                    .send();                        
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x(b)/$entity\","
                    + "\"@odata.id\":\""+baseURL+"/northwind/m/x('ABCDEFG')\","
                    + "\"b\":\"ABCDEFG\"}", response.getContentAsString());
            
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    @Test 
    public void testCrossJoin() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("UPDATE y SET b = 'a' WHERE y.a = 'a'", new int[] {1});
        teiid.addTranslator("x9", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);"
                    + "create foreign table y ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);");
            mmd.addSourceMapping("x9", "x9", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/$crossjoin(x,y)")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            String u = baseURL + "/northwind/m/";
            assertEquals("{\"@odata.context\":\"$metadata#Collection(Edm.ComplexType)\","
                    + "\"value\":[{\"x@odata.navigationLink\":\""+u+"x('ABCDEFG')\","
                            + "\"y@odata.navigationLink\":\""+u+"y('ABCDEFG')\"}]}", 
                            response.getContentAsString());

            // TODO: OLINGO-904
            /*
            response = http.newRequest(baseURL + "/northwind/m/$crossjoin(x,y)?$expand=x")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Collection(Edm.ComplexType)\",\"value\":[{\"x\":{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"},\"y@odata.navigationLink\":\""+u+"y('ABCDEFG')\"}]}", response.getContentAsString());
            */
            
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }     
    
    @Test 
    public void testNavigationLinks() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("UPDATE y SET b = 'a' WHERE y.a = 'a'", new int[] {1});
        teiid.addTranslator("x4", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);"
                    + "create foreign table y ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (b) REFERENCES x(a)"                    
                    + ") options (updatable true);");
            mmd.addSourceMapping("x4", "x4", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x('a')/y_FKX/$ref")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            String url = baseURL + "/northwind/m/";
            assertEquals("{\"@odata.context\":\"$metadata#Collection($ref)\","
                    + "\"value\":[{\"@odata.id\":\""+url+"y('ABCDEFG')\"}]}", 
                    response.getContentAsString());
            
            // update to collection based reference
            String payload = "{\n" +
                    "\"@odata.id\": \"/odata4/northwind/m/y('a')\"\n" +
                    "}";            
            response = http.newRequest(baseURL + "/northwind/m/x('a')/y_FKX/$ref")
                    .method("POST")
                    .content(new StringContentProvider(payload), ContentType.APPLICATION_JSON.toString())
                    .send();
            assertEquals(204, response.getStatus());
            

        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    @Test
    public void testRelatedEntities() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addData("SELECT x.a, x.b FROM x WHERE x.a = 'xa1'", Arrays.asList(Arrays.asList("xa1", "xb")));
        hc.addData("SELECT y.a, y.b FROM y WHERE y.b = 'xa1'", Arrays.asList(Arrays.asList("ya1", "xa1"), 
                Arrays.asList("ya2", "xa1")));

        // 1-many
        hc.addData("SELECT x.a FROM x WHERE x.a = 'xa2'", Arrays.asList(Arrays.asList("xa2")));
        hc.addData("SELECT y.a, y.b FROM y WHERE y.b = 'xa2'", new ArrayList<List<?>>());
        
        // 1-1
        hc.addData("SELECT z.a FROM z WHERE z.a = 'xa3'", Arrays.asList(Arrays.asList("xa3")));
        hc.addData("SELECT x.a, x.b FROM x WHERE x.a = 'xa3'", new ArrayList<List<?>>());
                
        teiid.addTranslator("x7", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);"
                    + "create foreign table y ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (b) REFERENCES x(a)"                    
                    + ") options (updatable true);"
                    + "create foreign table z ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (a) REFERENCES x(a)"                    
                    + ") options (updatable true);");
            
            mmd.addSourceMapping("x7", "x7", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = null;
            
            // single 1-many relation
            response = http.newRequest(baseURL + "/northwind/m/x('xa2')/y_FKX")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#y\",\"value\":[]}", response.getContentAsString());

            response = http.newRequest(baseURL + "/northwind/m/z('xa3')/FKX")
                    .method("GET")
                    .send();
            assertEquals(204, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @SuppressWarnings("unchecked")
    @Test 
    public void testExpandSimple() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        teiid.addTranslator("x7", hc);

        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);"
                    + "create foreign table y ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (b) REFERENCES x(a)"                    
                    + ") options (updatable true);"
                    + "create foreign table z ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (a) REFERENCES x(a)"                    
                    + ") options (updatable true);");
            
            mmd.addSourceMapping("x7", "x7", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = null;
            response = http.newRequest(baseURL + "/northwind/m/x?$expand=y_FKX")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"ABCDEFG\","
                    + "\"b\":\"ABCDEFG\",\"y_FKX\":[{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"}]}]}", 
                    response.getContentAsString());

            response = http.newRequest(baseURL + "/northwind/m/z?$expand=FKX&$select=a")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#z(a)\",\"value\":[{\"a\":\"ABCDEFG\","
                    + "\"FKX\":{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"}}]}", 
                    response.getContentAsString());
            
            // explictly selecting and expanding
            response = http.newRequest(baseURL + "/northwind/m/z?$expand=FKX&$select=a")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#z(a)\",\"value\":[{\"a\":\"ABCDEFG\","
                    + "\"FKX\":{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"}}]}", response.getContentAsString());

            response = http.newRequest(baseURL + "/northwind/m/z?$expand=FKX($select=a)")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#z(FKX(a))\",\"value\":[{\"a\":\"ABCDEFG\","
                    + "\"b\":\"ABCDEFG\",\"FKX\":{\"a\":\"ABCDEFG\"}}]}", response.getContentAsString());

            response = http.newRequest(baseURL + "/northwind/m/z?$expand=FKX")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#z\",\"value\":[{\"a\":\"ABCDEFG\","
                    + "\"b\":\"ABCDEFG\",\"FKX\":{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"}}]}", response.getContentAsString());

            response = http.newRequest(baseURL + "/northwind/m/z?$expand=FKX($top=1)")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());

            /* TODO
            response = http.newRequest(baseURL + "/northwind/m/z?$expand=FKX/a&$select=a")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#z(a,FKX/a)\",\"value\":[{\"a\":\"ABCDEFG\",\"FKX\":{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\"}}]}", response.getContentAsString());
            */
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    @SuppressWarnings("unchecked")
    @Test 
    public void testExpandSimple2() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addData("SELECT x.a, x.b FROM x", Arrays.asList(Arrays.asList("xa1", "xb"),
                Arrays.asList("xa2", "xb2")));
        hc.addData("SELECT y.a, y.b FROM y WHERE y.b = 'xa1'", Arrays.asList(Arrays.asList("ya1", "xa1"), 
                Arrays.asList("ya2", "xa1")));
        teiid.addTranslator("x7", hc);

        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a)"
                    + ") options (updatable true);"
                    + "create foreign table y ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (b) REFERENCES x(a)"                    
                    + ") options (updatable true);"
                    + "create foreign table z ("
                    + " a string, "
                    + " b string, "
                    + " primary key (a),"
                    + " CONSTRAINT FKX FOREIGN KEY (a) REFERENCES x(a)"                    
                    + ") options (updatable true);");
            
            mmd.addSourceMapping("x7", "x7", null);
            teiid.deployVDB("northwind", mmd);

            ContentResponse response = null;
            response = http.newRequest(baseURL + "/northwind/m/x?$expand="+Encoder.encode("y_FKX($filter=b eq 'xa1')"))
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\","
                    + "\"value\":["
                    + "{\"a\":\"xa1\",\"b\":\"xb\","
                    + "\"y_FKX\":[{\"a\":\"ya1\",\"b\":\"xa1\"},"
                    + "{\"a\":\"ya2\",\"b\":\"xa1\"}]},"
                    + "{\"a\":\"xa2\",\"b\":\"xb2\","
                    + "\"y_FKX\":[]}]}", 
                    response.getContentAsString());

        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }     
    
    @Test
    public void testBatch() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addUpdate("DELETE FROM x WHERE x.a = 'a' AND x.b = 'b'", new int[] {1});
        teiid.addTranslator("x", hc);
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b string, c integer, "
                    + "primary key (a, b)) options (updatable true);");
            mmd.addSourceMapping("x", "x", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            final String batch = ""
                    + "--batch_8194-cf13-1f56" + CRLF
                    + MIME_HEADERS
                    + CRLF
                    + "GET "+baseURL+"/northwind/m/x HTTP/1.1" + CRLF
                    + "Accept: application/json" + CRLF
                    + "MaxDataServiceVersion: 4.0" + CRLF
                    + CRLF
                    + CRLF
                    + "--batch_8194-cf13-1f56" + CRLF
                    + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
                    + CRLF
                    + "--changeset_f980-1cb6-94dd" + CRLF
                    + "content-type:     Application/http" + CRLF
                    + "content-transfer-encoding: Binary" + CRLF
                    + "Content-ID: 1" + CRLF
                    + CRLF
                    + "DELETE "+baseURL+"/northwind/m/x(a='a',b='b') HTTP/1.1" + CRLF
                    + "Content-type: application/json" + CRLF
                    + CRLF
                    + CRLF
                    + "--changeset_f980-1cb6-94dd--" + CRLF
                    + "--batch_8194-cf13-1f56--";
            
            ContentResponse response = http.newRequest(baseURL + "/northwind/m/$batch")
                    .method("POST")
                    .content(new StringContentProvider(batch), "multipart/mixed;boundary=batch_8194-cf13-1f56")
                    .send();

            assertEquals(202, response.getStatus());
            /*
            String expected = "--batch_d06279e4-c510-46ed-a778-e4e941dfd6f1\n" + 
                    "Content-Type: application/http\n" + 
                    "Content-Transfer-Encoding: binary\n" + 
                    "\n" + 
                    "HTTP/1.1 200 OK\n" + 
                    "Content-Type: application/json;odata.metadata=minimal\n" + 
                    "Content-Length: 78\n" + 
                    "\n" + 
                    "{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"ABCDEFG\",\"b\":\"ABCDEFG\",\"c\":0}]}\n" + 
                    "--batch_d06279e4-c510-46ed-a778-e4e941dfd6f1\n" + 
                    "Content-Type: multipart/mixed; boundary=changeset_5a1cba47-b51f-46c2-b0ac-ead23fa7706d\n" + 
                    "\n" + 
                    "--changeset_5a1cba47-b51f-46c2-b0ac-ead23fa7706d\n" + 
                    "Content-Type: application/http\n" + 
                    "Content-Transfer-Encoding: binary\n" + 
                    "Content-Id: 1\n" + 
                    "\n" + 
                    "HTTP/1.1 204 No Content\n" + 
                    "Content-Length: 0\n" + 
                    "\n" + 
                    "\n" + 
                    "--changeset_5a1cba47-b51f-46c2-b0ac-ead23fa7706d--\n" + 
                    "--batch_d06279e4-c510-46ed-a778-e4e941dfd6f1--";
            assertEquals(expected, response.getContentAsString());
            */
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }

    static class ODataHardCodedExecutionFactory extends HardCodedExecutionFactory{
        @Override
        public boolean supportsCompareCriteriaEquals() {
            return true;
        }
        @Override
        protected List<? extends List<?>> getData(
                QueryExpression command) {
                            
            if (super.getData(command) != null) {
                List<? extends List<?>> results = super.getData(command);
                return results;
            }
            
            Class<?>[] colTypes = command.getProjectedQuery().getColumnTypes();
            List<Expression> cols = new ArrayList<Expression>();
            for (int i = 0; i < colTypes.length; i++) {
                ElementSymbol elementSymbol = new ElementSymbol("X");
                elementSymbol.setType(colTypes[i]);
                cols.add(elementSymbol);
            }
            return (List)Arrays.asList(AutoGenDataService.createResults(cols, 1, false));
        }         
    }
    
    private HardCodedExecutionFactory buildHardCodedExecutionFactory() {
        return new ODataHardCodedExecutionFactory();
    }
    
    @Test 
    public void testJsonProcedureResultSet() throws Exception {
        try {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory();
        hc.addData("EXEC x()", Arrays.asList(Arrays.asList("x"), Arrays.asList("y")));
        teiid.addTranslator("x2", hc);

        ModelMetaData mmd = new ModelMetaData();
        mmd.setName("m");
        mmd.addSourceMetadata("ddl", "create foreign procedure x () returns table(y string) OPTIONS(UPDATECOUNT 0);");
        mmd.addSourceMapping("x2", "x2", null);
        teiid.deployVDB("northwind", mmd);

        localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

        ContentResponse response =  http.GET(baseURL + "/northwind/m/x()?$format=json");
        assertEquals(200, response.getStatus());
        assertEquals("{\"@odata.context\":\"$metadata#Collection(northwind.1.m.x_RSParam)\","
                + "\"value\":[{\"y\":\"x\"},{\"y\":\"y\"}]}", response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }

    @Test 
    public void testBasicTypes() throws Exception {
        try {
        ModelMetaData mmd = new ModelMetaData();
        mmd.setName("m");
        mmd.addSourceMapping("x3", "x3", null);
        
        MetadataStore ms = RealMetadataFactory.exampleBQTStore();
        Schema s = ms.getSchema("BQT1");
        KeyRecord pk = new KeyRecord(KeyRecord.Type.Primary);
        Table smalla = s.getTable("SmallA");
        pk.setName("pk");
        pk.addColumn(smalla.getColumnByName("IntKey"));
        smalla.setPrimaryKey(pk);
        String ddl = DDLStringVisitor.getDDLString(s, EnumSet.allOf(SchemaObjectType.class), "SmallA");
        
        mmd.addSourceMetadata("DDL", ddl);
        
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        teiid.addTranslator("x3", hc);
        teiid.deployVDB("northwind", mmd);

        localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

        ContentResponse response= http.GET(baseURL + "/northwind/m/SmallA?$format=json&$select=TimeValue");
        assertEquals(200, response.getStatus());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testCompositeKeyTimestamp() throws Exception {
        HardCodedExecutionFactory hc = buildHardCodedExecutionFactory();
        hc.addData("SELECT x.a, x.b, x.c FROM x WHERE x.a = 'a' AND x.b = {ts '2011-09-11T00:00:00'}", 
                Arrays.asList(Arrays.asList("a", TimestampUtil.createTimestamp(111, 8, 11, 0, 0, 0, 0), 1)));
        hc.addUpdate("INSERT INTO x (a, b) VALUES ('b', {ts '2000-02-02 22:22:22.0'})", new int[] {1});
        
        teiid.addTranslator("x1", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b timestamp, c integer, "
                    + "primary key (a, b)) options (updatable true);");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x")
                    .method("POST")
                    .content(new StringContentProvider("{\"a\":\"b\", \"b\":\"2000-02-02T22:22:22Z\"}"), 
                            "application/json")
                    .send();                        
            assertEquals(201, response.getStatus());
            
            response = http.newRequest(baseURL + "/northwind/m/x(a='a',b=2011-09-11T00:00:00Z)")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());

        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test
    public void testStreamProperties() throws Exception {
        ContentResponse response = http.newRequest(baseURL + "/loopy/vm1/LobTable(2)/e2")
                .method("GET")
                .send();
        assertEquals(200, response.getStatus());
        assertEquals("<name>content2</name>", 
                response.getContentAsString());      
        
        response = http.newRequest(baseURL + "/loopy/vm1/LobTable")
                .method("GET")
                .send();
        assertEquals(200, response.getStatus());
        String string = response.getContentAsString();
        assertTrue(string.contains("odata4/loopy/vm1/LobTable(1)/e2"));
        assertTrue(string.contains("odata4/loopy/vm1/LobTable(2)/e2"));
    } 
    
    @Test
    public void testWithAlternateContext() throws Exception {
    	http.stop();
    	server.stop();
    	Map<String, String> props = new HashMap<String, String>();
    	props.put("vdb-name", "loopy");
    	props.put("vdb-version", "1");
    	
    	createContext("/other", props);
    	
        ContentResponse response = http.newRequest(baseURL + "/vm1/LobTable(2)/e2")
                .method("GET")
                .send();
        assertEquals(200, response.getStatus());
        assertEquals("<name>content2</name>", 
                response.getContentAsString());      
    } 
    
    @Test 
    public void testNonExistentEntity() throws Exception {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory();
        hc.addData("SELECT x.a, x.b FROM x", Arrays.asList(Arrays.asList("a", 1)));
        
        teiid.addTranslator("x1", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b integer, primary key (a));");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x('b')")
                    .method("GET")
                    .send();
            assertEquals(404, response.getStatus());
            
            response = http.newRequest(baseURL + "/northwind/m/x('b')/b")
                    .method("GET")
                    .send();
            assertEquals(404, response.getStatus());

        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test
    public void testInvalidResource() throws Exception {
        http.stop();
        server.stop();
        Map<String, String> props = new HashMap<String, String>();
        props.put("vdb-name", "loopy");
        props.put("vdb-version", "1");
        
        createContext("/other", props);
        
        ContentResponse response = http.newRequest(baseURL + "/vm1/Foo")
                .method("GET")
                .send();
        assertEquals(404, response.getStatus());
        assertEquals("{\"error\":{\"code\":null,\"message\":\"Cannot find EntitySet, Singleton, "
                + "ActionImport or FunctionImport with name 'Foo'.\"}}",  
                response.getContentAsString());      
    }
    
    @Test 
    public void testErrorCodes() throws Exception {        
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory() {
            @Override
            public ResultSetExecution createResultSetExecution(
                    final QueryExpression command, ExecutionContext executionContext,
                    RuntimeMetadata metadata, Object connection)
                    throws TranslatorException {
                List<? extends List<?>> list = getData(command);
                if (list == null) {
                    throw new RuntimeException(command.toString());
                }
                final Iterator<? extends List<?>> result = list.iterator();
                return new ResultSetExecution() {
                    
                    @Override
                    public void execute() throws TranslatorException {
                        throw new TranslatorException(ODataPlugin.Event.TEIID16001, "execution failed");
                    }
                    
                    @Override
                    public void close() {
                        
                    }
                    
                    @Override
                    public void cancel() throws TranslatorException {
                        
                    }
                    
                    @Override
                    public List<?> next() throws TranslatorException, DataNotAvailableException {
                        if (result.hasNext()) {
                            return result.next();
                        }
                        return null;
                    }
                };
            }            
        };
        hc.addData("SELECT x.a, x.b FROM x", Arrays.asList(Arrays.asList("a", 1)));
        
        teiid.addTranslator("x1", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", "create foreign table x (a string, b integer, primary key (a));");
            mmd.addSourceMapping("x1", "x1", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = http.newRequest(baseURL + "/northwind/m/x")
                    .method("GET")
                    .send();
            assertEquals(400, response.getStatus());
            assertEquals("{\"error\":{\"code\":\"TEIID30504\","
                    + "\"message\":\"TEIID30504 x1: TEIID16001 execution failed\"}}", 
                    response.getContentAsString());

            response = http.newRequest(baseURL + "/northwind/m/x?$format=xml")
                    .method("GET")
                    .send();
            assertEquals(400, response.getStatus());
            assertEquals("<?xml version='1.0' encoding='UTF-8'?>"
                    + "<error xmlns=\"http://docs.oasis-open.org/odata/ns/metadata\">"
                    +   "<code>TEIID30504</code>"
                    +   "<message>TEIID30504 x1: TEIID16001 execution failed</message>"
                    + "</error>", 
                    response.getContentAsString());
            
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testFilterNull() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) as "
                    + "select 'xyz', 123 union all "
                    + "select 'abc', null;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$filter="+Encoder.encode("b eq null"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"abc\",\"b\":null}]}", 
                    response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    
    @Test
    public void testMultipleAirthamatic() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/pm1/G1?$filter="+Encoder.encode("e2 eq 1 add 1 add 1"));
        assertEquals(200, response.getStatus());
    }

    @Test
    public void testFloor() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/pm1/G1?$filter="+Encoder.encode("e2 eq floor(4.2)"));
        assertEquals(200, response.getStatus());
    }
    
    @Test
    public void testRound() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/pm1/G1?$filter="+Encoder.encode("e2 eq round(4.2)"));
        assertEquals(200, response.getStatus());
    }
    
    @Test
    public void test$allNotImplemented() throws Exception {
        ContentResponse response = http.GET(baseURL + "/loopy/vm1/$all");
        assertEquals(501, response.getStatus());
    } 
    
    @SuppressWarnings("unchecked")
    @Test 
    public void testExpand() throws Exception {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory();
        hc.addData("SELECT Customers.id, Customers.name FROM Customers", 
                Arrays.asList(Arrays.asList(1, "customer1"), Arrays.asList(2, "customer2"),
                Arrays.asList(3, "customer3"), Arrays.asList(4, "customer4")));
        hc.addData("SELECT Orders.customerid, Orders.id, Orders.place FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1, "town"), Arrays.asList(1, 2, "state"),
                Arrays.asList(1, 3, "country"), Arrays.asList(1,4, "abroad"),
                Arrays.asList(2, 5, "state"), Arrays.asList(2, 6, "country"),
                Arrays.asList(3,7,"town"), Arrays.asList(3, 8, "town")));
        hc.addData("SELECT Orders.customerid, Orders.place, Orders.id FROM Orders", 
                Arrays.asList(Arrays.asList(1, "town", 1), Arrays.asList(1, "state", 2),
                Arrays.asList(1, "country",3), Arrays.asList(1,"abroad", 4),
                Arrays.asList(2, "state", 5), Arrays.asList(2, "country", 6),
                Arrays.asList(3,"town", 7), Arrays.asList(3, "town", 8)));
        hc.addData("SELECT Orders.place, Orders.customerid, Orders.id FROM Orders", 
                Arrays.asList(Arrays.asList("town",1, 1), Arrays.asList("state", 1, 2),
                Arrays.asList("country",1, 3), Arrays.asList("abroad", 1, 4),
                Arrays.asList("state", 2, 5), Arrays.asList("country", 2, 6),
                Arrays.asList("town", 3, 7), Arrays.asList("town", 3, 8)));
        
        
        teiid.addTranslator("x12", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", 
                    "CREATE FOREIGN TABLE Customers (\n" + 
                    "  id integer PRIMARY KEY OPTIONS (NAMEINSOURCE 'id'),\n" + 
                    "  name varchar(10));\n" + 
                    "CREATE FOREIGN TABLE Orders (\n" + 
                    "  id integer PRIMARY KEY OPTIONS (NAMEINSOURCE 'id'),\n" + 
                    "  customerid integer,\n" + 
                    "  place varchar(10),\n" + 
                    "  FOREIGN KEY (customerid) REFERENCES Customers(id));");
            
            mmd.addSourceMapping("x12", "x12", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = null;
            
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0&$count=true")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"@odata.count\":4,\""
                    + "value\":["
                    + "{\"id\":1,\"name\":\"customer1\","
                        + "\"Orders_FK0\":["
                            + "{\"id\":1,\"customerid\":1,\"place\":\"town\"},"
                            + "{\"id\":2,\"customerid\":1,\"place\":\"state\"},"
                            + "{\"id\":3,\"customerid\":1,\"place\":\"country\"},"
                            + "{\"id\":4,\"customerid\":1,\"place\":\"abroad\"}"
                    + "]},"
                    + "{\"id\":2,\"name\":\"customer2\","
                        + "\"Orders_FK0\":["
                            + "{\"id\":5,\"customerid\":2,\"place\":\"state\"},"
                            + "{\"id\":6,\"customerid\":2,\"place\":\"country\"}"
                    + "]},"
                    + "{\"id\":3,\"name\":\"customer3\","
                        + "\"Orders_FK0\":["
                            + "{\"id\":7,\"customerid\":3,\"place\":\"town\"},"
                            + "{\"id\":8,\"customerid\":3,\"place\":\"town\"}"
                    + "]},"
                    + "{\"id\":4,\"name\":\"customer4\","
                        + "\"Orders_FK0\":["
                        + "]}]}", 
                    response.getContentAsString());
            
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0&$count=true&$skip=3")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"@odata.count\":4,\"value\":["
                    + "{\"id\":4,\"name\":\"customer4\","
                    + "\"Orders_FK0\":[]"
                    + "}]}", response.getContentAsString());
            
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0&$skip=2")
                    .method("GET")
                    .header("Prefer", "odata.maxpagesize=1")
                    .send();
            assertEquals(200, response.getStatus());
            assertTrue(response.getContentAsString().startsWith("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"value\":[{\"id\":3,\"name\":\"customer3\","
                    + "\"Orders_FK0\":["
                    + "{\"id\":7,\"customerid\":3,\"place\":\"town\"},"
                    + "{\"id\":8,\"customerid\":3,\"place\":\"town\"}"
                    + "]}],"
                    + "\"@odata.nextLink\":\"http://localhost:"));
            assertTrue(response.getContentAsString(), 
                    response.getContentAsString().endsWith(",8\"}"));
            
            JsonParser parser = new JsonFactory(new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true))
                .createParser(response.getContentAsString());
            JsonNode node = parser.getCodec().readTree(parser);
            
            response = http.newRequest(node.get("@odata.nextLink").asText())
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"value\":["
                    + "{\"id\":4,\"name\":\"customer4\","
                    + "\"Orders_FK0\":[]"
                    + "}]}", response.getContentAsString());

            // system options
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0($skip=2)&$count=true")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"@odata.count\":4,\""
                    + "value\":["
                    + "{\"id\":1,\"name\":\"customer1\","
                        + "\"Orders_FK0\":["
                            + "{\"id\":3,\"customerid\":1,\"place\":\"country\"},"
                            + "{\"id\":4,\"customerid\":1,\"place\":\"abroad\"}"
                    + "]},"
                    + "{\"id\":2,\"name\":\"customer2\","
                        + "\"Orders_FK0\":["
                    + "]},"
                    + "{\"id\":3,\"name\":\"customer3\","
                        + "\"Orders_FK0\":["
                    + "]},"
                    + "{\"id\":4,\"name\":\"customer4\","
                        + "\"Orders_FK0\":["
                        + "]}]}", 
                    response.getContentAsString());     
            
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0($top=2;$count=true)&$count=true")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"@odata.count\":4,\""
                    + "value\":["
                        + "{\"id\":1,\"name\":\"customer1\","
                            + "\"Orders_FK0@odata.count\":4,"
                            + "\"Orders_FK0\":["
                                + "{\"id\":1,\"customerid\":1,\"place\":\"town\"},"
                                + "{\"id\":2,\"customerid\":1,\"place\":\"state\"}"
                            + "]},"
                        + "{\"id\":2,\"name\":\"customer2\","
                        + "\"Orders_FK0@odata.count\":2,"
                        + "\"Orders_FK0\":["
                            + "{\"id\":5,\"customerid\":2,\"place\":\"state\"},"
                            + "{\"id\":6,\"customerid\":2,\"place\":\"country\"}"
                        + "]},"
                        + "{\"id\":3,\"name\":\"customer3\","
                        + "\"Orders_FK0@odata.count\":2,"
                        + "\"Orders_FK0\":["
                        + "{\"id\":7,\"customerid\":3,\"place\":\"town\"},"
                        + "{\"id\":8,\"customerid\":3,\"place\":\"town\"}"
                        + "]},"
                        + "{\"id\":4,\"name\":\"customer4\","
                        + "\"Orders_FK0@odata.count\":0,"
                        + "\"Orders_FK0\":["
                        + "]}]}", 
                    response.getContentAsString());     
            
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0($top=1;$select=place)&$count=true")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers(Orders_FK0(place))\","
                    + "\"@odata.count\":4,\""
                    + "value\":["
                    + "{\"id\":1,\"name\":\"customer1\","
                        + "\"Orders_FK0\":["
                        + "{\"@odata.id\":\""+baseURL+"/northwind/m/Orders(1)\",\"place\":\"town\"}"
                    + "]},"
                    + "{\"id\":2,\"name\":\"customer2\","
                        + "\"Orders_FK0\":["
                        + "{\"@odata.id\":\""+baseURL+"/northwind/m/Orders(5)\",\"place\":\"state\"}"
                    + "]},"
                    + "{\"id\":3,\"name\":\"customer3\","
                        + "\"Orders_FK0\":["
                        + "{\"@odata.id\":\""+baseURL+"/northwind/m/Orders(7)\",\"place\":\"town\"}"
                    + "]},"
                    + "{\"id\":4,\"name\":\"customer4\","
                        + "\"Orders_FK0\":["
                        + "]}]}", 
                    response.getContentAsString());
            
            response = http.newRequest(baseURL + "/northwind/m/Customers?$expand=Orders_FK0($filter="+Encoder.encode("place eq ")+"'town')")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
                    + "\"value\":[{\"id\":1,\"name\":\"customer1\","
                    +   "\"Orders_FK0\":[{\"id\":1,\"customerid\":1,\"place\":\"town\"}]},"
                    + "{\"id\":2,\"name\":\"customer2\",\"Orders_FK0\":[]},"
                    + "{\"id\":3,\"name\":\"customer3\","
                    +  "\"Orders_FK0\":[{\"id\":7,\"customerid\":3,\"place\":\"town\"},"
                    + "{\"id\":8,\"customerid\":3,\"place\":\"town\"}]},"
                    + "{\"id\":4,\"name\":\"customer4\",\"Orders_FK0\":[]}]}", 
                    response.getContentAsString());                  
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testIndexingOfStringFunctions() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "create view x (a string primary key, b integer) as "
                    + "select 'xyz', 123 union all select 'abc', 456;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/x?$filter="
                    +Encoder.encode("indexof(a,'y') eq 1"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"xyz\",\"b\":123}]}", 
                    response.getContentAsString());
            
            response = http.GET(baseURL + "/northwind/vw/x?$filter="
                    +Encoder.encode("indexof(a,'y') eq 2"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[]}", 
                    response.getContentAsString());

            response = http.GET(baseURL + "/northwind/vw/x?$filter="
                    +Encoder.encode("substring(a,1) eq 'yz'"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"xyz\",\"b\":123}]}", 
                    response.getContentAsString());
            
            response = http.GET(baseURL + "/northwind/vw/x?$filter="
                    +Encoder.encode("substring(a,1,2) eq 'yz'"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"xyz\",\"b\":123}]}", 
                    response.getContentAsString());
            
            response = http.GET(baseURL + "/northwind/vw/x?$filter="
                    +Encoder.encode("substring(a,0,1) eq 'a'"));
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#x\",\"value\":[{\"a\":\"abc\",\"b\":456}]}", 
                    response.getContentAsString());            
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }    
    
    @Test 
    public void testMonthFunctions() throws Exception {
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("vw");
            mmd.addSourceMetadata("ddl", "CREATE VIEW SimpleTable(\n" + 
                    "    intkey integer PRIMARY KEY,\n" + 
                    "    intnum integer,\n" + 
                    "    stringkey varchar(20),\n" + 
                    "    stringval varchar(20),\n" + 
                    "    booleanval boolean,\n" + 
                    "    decimalval decimal(20, 10),\n" + 
                    "    timeval time,\n" + 
                    "    dateval date,\n" + 
                    "    timestampval timestamp,\n" + 
                    "    clobval clob) as select 1,1, '1','1',true,1.0,{t '00:01:01'}, "
                    + "{d '2001-01-01'},{ts '2001-01-01 00:01:01.01'},null;");
            mmd.setModelType(Model.Type.VIRTUAL);
            teiid.deployVDB("northwind", mmd);

            Properties props = new Properties();
            localClient = getClient(teiid.getDriver(), "northwind", 1, props);
            
            ContentResponse response = http.GET(baseURL + "/northwind/vw/SimpleTable?$filter="
                    +Encoder.encode("month(2001-01-01T00:01:01.01Z) eq intkey")+"&$select=intkey");
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#SimpleTable(intkey)\",\"value\":[{\"intkey\":1}]}", 
                    response.getContentAsString());
            
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");
        }
    }
    
    @Test 
    public void testReverseNavigation() throws Exception {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory();
        hc.addData("SELECT Customers.id, Customers.name FROM Customers", 
                Arrays.asList(Arrays.asList(1, "customer1"), Arrays.asList(2, "customer2"),
                Arrays.asList(3, "customer3"), Arrays.asList(4, "customer4")));


        hc.addData("SELECT Customers.id FROM Customers", 
                Arrays.asList(Arrays.asList(1), Arrays.asList(2),
                Arrays.asList(3), Arrays.asList(4)));
        hc.addData("SELECT Orders.id, Orders.customerid FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1), Arrays.asList(2, 1),
                Arrays.asList(3,1), Arrays.asList(4,1),
                Arrays.asList(5,2), Arrays.asList(6,2),
                Arrays.asList(7,3), Arrays.asList(8,3)));
        hc.addData("SELECT Orders.id, Orders.customerid, Orders.place FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1, "town"), Arrays.asList(2, 1, "state"),
                Arrays.asList(3, 1,"country"), Arrays.asList(4, 1, "abroad"),
                Arrays.asList(5,2, "state"), Arrays.asList(6,2, "country"),
                Arrays.asList(7,3,"town"), Arrays.asList(8,3, "town")));

        hc.addData("SELECT Orders.customerid, Orders.id, Orders.place FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1, "town"), Arrays.asList(1, 2, "state"),
                Arrays.asList(1,3, "country"), Arrays.asList(1, 4, "abroad"),
                Arrays.asList(2,5, "state"), Arrays.asList(2, 6, "country"),
                Arrays.asList(3,7,"town"), Arrays.asList(3,8, "town")));        
        
        
        teiid.addTranslator("x12", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", 
                    "CREATE FOREIGN TABLE Customers (\n" + 
                    "  id integer PRIMARY KEY OPTIONS (NAMEINSOURCE 'id'),\n" + 
                    "  name varchar(10));\n" + 
                    "CREATE FOREIGN TABLE Orders (\n" + 
                    "  id integer PRIMARY KEY OPTIONS (NAMEINSOURCE 'id'),\n" + 
                    "  customerid integer,\n" + 
                    "  place varchar(10),\n" + 
                    "  CONSTRAINT Customer FOREIGN KEY (customerid) REFERENCES Customers(id));");
            
            mmd.addSourceMapping("x12", "x12", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind",1,new Properties());

            ContentResponse response = null;
            response = http.newRequest(baseURL + "/northwind/m/Orders(1)/Customer")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers/$entity\","
                    + "\"id\":1,\"name\":\"customer1\"}", 
                    response.getContentAsString());
            
            response = http.newRequest(baseURL + "/northwind/m/Orders(1)?$expand=Customer")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Orders/$entity\","
                    + "\"id\":1,\"customerid\":1,\"place\":\"town\","
                    + "\"Customer\":{\"id\":1,\"name\":\"customer1\"}}", 
                    response.getContentAsString());
            response = http.newRequest(baseURL + "/northwind/m/Orders(1)/Customer/Orders_Customer")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
            		+ "\"value\":[{\"id\":1,\"customerid\":1,\"place\":\"town\"},"
            		+ "{\"id\":2,\"customerid\":1,\"place\":\"state\"},"
            		+ "{\"id\":3,\"customerid\":1,\"place\":\"country\"},"
            		+ "{\"id\":4,\"customerid\":1,\"place\":\"abroad\"}]}", 
                    response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");            
        }
    }
    
    @Test 
    public void testReverseNavigationWithUniqueKey() throws Exception {
        HardCodedExecutionFactory hc = new HardCodedExecutionFactory();
        hc.addData("SELECT Customers.id, Customers.name FROM Customers", 
                Arrays.asList(Arrays.asList(1, "customer1"), Arrays.asList(2, "customer2"),
                Arrays.asList(3, "customer3"), Arrays.asList(4, "customer4")));


        hc.addData("SELECT Customers.id FROM Customers", 
                Arrays.asList(Arrays.asList(1), Arrays.asList(2),
                Arrays.asList(3), Arrays.asList(4)));
        hc.addData("SELECT Orders.id, Orders.customerid FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1), Arrays.asList(2, 1),
                Arrays.asList(3,1), Arrays.asList(4,1),
                Arrays.asList(5,2), Arrays.asList(6,2),
                Arrays.asList(7,3), Arrays.asList(8,3)));
        hc.addData("SELECT Orders.id, Orders.customerid, Orders.place FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1, "town"), Arrays.asList(2, 1, "state"),
                Arrays.asList(3, 1,"country"), Arrays.asList(4, 1, "abroad"),
                Arrays.asList(5,2, "state"), Arrays.asList(6,2, "country"),
                Arrays.asList(7,3,"town"), Arrays.asList(8,3, "town")));

        hc.addData("SELECT Orders.customerid, Orders.id, Orders.place FROM Orders", 
                Arrays.asList(Arrays.asList(1, 1, "town"), Arrays.asList(1, 2, "state"),
                Arrays.asList(1,3, "country"), Arrays.asList(1, 4, "abroad"),
                Arrays.asList(2,5, "state"), Arrays.asList(2, 6, "country"),
                Arrays.asList(3,7,"town"), Arrays.asList(3,8, "town")));        
        
        
        teiid.addTranslator("x12", hc);
        
        try {
            ModelMetaData mmd = new ModelMetaData();
            mmd.setName("m");
            mmd.addSourceMetadata("ddl", 
                    "CREATE FOREIGN TABLE Customers (\n" + 
                    "  id integer UNIQUE OPTIONS (NAMEINSOURCE 'id'),\n" + 
                    "  name varchar(10));\n" + 
                    "CREATE FOREIGN TABLE Orders (\n" + 
                    "  id integer PRIMARY KEY OPTIONS (NAMEINSOURCE 'id'),\n" + 
                    "  customerid integer,\n" + 
                    "  place varchar(10),\n" + 
                    "  CONSTRAINT Customer FOREIGN KEY (customerid) REFERENCES Customers(id));");
            
            mmd.addSourceMapping("x12", "x12", null);
            teiid.deployVDB("northwind", mmd);

            localClient = getClient(teiid.getDriver(), "northwind", 1, new Properties());

            ContentResponse response = null;
            response = http.newRequest(baseURL + "/northwind/m/Orders(1)/Customer")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers/$entity\","
                    + "\"id\":1,\"name\":\"customer1\"}", 
                    response.getContentAsString());
            
            response = http.newRequest(baseURL + "/northwind/m/Orders(1)?$expand=Customer")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Orders/$entity\","
                    + "\"id\":1,\"customerid\":1,\"place\":\"town\","
                    + "\"Customer\":{\"id\":1,\"name\":\"customer1\"}}", 
                    response.getContentAsString());
            response = http.newRequest(baseURL + "/northwind/m/Orders(1)/Customer/Orders_Customer")
                    .method("GET")
                    .send();
            assertEquals(200, response.getStatus());
            assertEquals("{\"@odata.context\":\"$metadata#Customers\","
            		+ "\"value\":[{\"id\":1,\"customerid\":1,\"place\":\"town\"},"
            		+ "{\"id\":2,\"customerid\":1,\"place\":\"state\"},"
            		+ "{\"id\":3,\"customerid\":1,\"place\":\"country\"},"
            		+ "{\"id\":4,\"customerid\":1,\"place\":\"abroad\"}]}", 
                    response.getContentAsString());
        } finally {
            localClient = null;
            teiid.undeployVDB("northwind");            
        }
    }
}