/*
Derby - Class org.apache.derbyTesting.functionTests.tests.lang.OuterJoinTest

Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

 */
package org.apache.derbyTesting.functionTests.tests.lang;

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLWarning;

import java.sql.Timestamp;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.RuntimeStatisticsParser;
import org.apache.derbyTesting.junit.TestConfiguration;

public final class OuterJoinTest extends BaseJDBCTestCase
{

    /**
     * Public constructor required for running test as standalone JUnit.
     */
    public OuterJoinTest(String name)
    {
        super(name);
    }

    public static Test suite()
    {
        //Add the test case into the test suite
        TestSuite suite = new TestSuite("OuterJoinTest Test");
        return TestConfiguration.defaultSuite(OuterJoinTest.class);
    }

    private void createTestObjects(Statement st) throws Exception
    {
        setAutoCommit(false);

        //clean the database tests in previously created
        CleanDatabaseTestSetup.cleanDatabase(getConnection(), false);

        // create some tables
        
        st.executeUpdate("create table t1(c1 int)");
        st.executeUpdate("create table t2(c1 int)");
        st.executeUpdate("create table t3(c1 int)");
        st.executeUpdate("create table tt1(c1 int, c2 int, c3 int)");
        st.executeUpdate("create table tt2(c1 int, c2 int, c3 int)");
        st.executeUpdate("create table tt3(c1 int, c2 int, c3 int)");
        st.executeUpdate("create table empty_table(c1 int)");
        st.executeUpdate("create table insert_test(c1 int, c2 int, c3 int)");
        st.executeUpdate("create table x (c1 int, c2 int, c3 int)");
        st.executeUpdate("create table y (c3 int, c4 int, c5 int)");
        st.executeUpdate("create table a (c1 int)");
        st.executeUpdate("create table b (c2 float)");
        st.executeUpdate("create table c (c3 char(30))");
        // following is verifying that oj is not a keyword
        st.executeUpdate("create table oj(oj int)");
        
        // populate the tables
        
        st.executeUpdate("insert into t1 values 1, 2, 2, 3, 4");
        st.executeUpdate("insert into t2 values 1, 3, 3, 5, 6");
        st.executeUpdate("insert into t3 values 2, 3, 5, 5, 7");
        st.executeUpdate("insert into tt1 select c1, c1, c1 from t1");
        st.executeUpdate("insert into tt2 select c1, c1, c1 from t2");
        st.executeUpdate("insert into tt3 select c1, c1, c1 from t3");
        st.executeUpdate("insert into x values (1, 2, 3), (4, 5, 6)");
        st.executeUpdate("insert into y values (3, 4, 5), (666, 7, 8)");
        st.executeUpdate(
            "insert into insert_test"
            + "(select * from t1 a left outer join t2 b on a.c1 = "
            + "b.c1 left outer join t3 c on a.c1 <> c.c1)");

        st.executeUpdate("insert into a values 1");
        st.executeUpdate("insert into b values 3.3");
        st.executeUpdate("insert into c values 'asdf'");
        // verifying that oj is not a keyword
        st.executeUpdate("insert into oj(oj) values (1)");
    }

    // negative tests on outer join 
    public void testNegative() throws Exception
    {
        Statement st = createStatement();

        createTestObjects(st);
        
        assertStatementError("42X01", st,
            "select * from t1 outer join t2");
        
        // no join clause
        
        assertStatementError("42X01", st,
            "select * from t1 left outer join t2");
        
        assertStatementError("42X01", st,
            " select * from t1 right outer join t2");
        }

    // positive tests on normal forms of outer join
    public void testPositive() throws Exception
    {
        Statement st = createStatement();
        ResultSet rs = null;
        ResultSetMetaData rsmd;
        PreparedStatement pSt;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);
                
        rs = st.executeQuery(
            "select t1.c1 from t1 left outer join t2 on t1.c1 = t2.c1");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"3"},
            {"4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select t2.c1 from t1 right outer join t2 on t1.c1 = t2.c1");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"3"},
            {"3"},
            {"5"},
            {"6"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select a.x from t1 a (x) left outer join t2 b (x) "
            + "on a.x = b.x");
        
        expColNames = new String [] {"X"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"3"},
            {"4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        // verify that selects from inner table work
        
        rs = st.executeQuery(
            "select b.* from (values 9) a left outer join t2 b on 1=1");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"3"},
            {"3"},
            {"5"},
            {"6"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select b.* from (values 9) a left outer join t2 b on 1=0");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select b.* from (values 9) a right outer join t2 b on 1=0");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"3"},
            {"3"},
            {"5"},
            {"6"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select a.* from (values 9) a right outer join t2 b on 1=1");
        
        expColNames = new String [] {"1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"9"},
            {"9"},
            {"9"},
            {"9"},
            {"9"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select a.* from (values 9) a right outer join t2 b on 1=0");
        
        expColNames = new String [] {"1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {null},
            {null},
            {null},
            {null},
            {null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select a.* from ((values ('a', 'b')) a inner join "
            + "(values ('c', 'd')) b on 1=1) left outer join "
            + "(values ('e', 'f')) c on 1=1");
        
        expColNames = new String [] {"1", "2"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"a", "b"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select b.* from ((values ('a', 'b')) a inner join "
            + "(values ('c', 'd')) b on 1=1) left outer join "
            + "(values ('e', 'f')) c on 1=1");
        
        expColNames = new String [] {"1", "2"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"c", "d"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select c.* from ((values ('a', 'b')) a inner join "
            + "(values ('c', 'd')) b on 1=1) left outer join "
            + "(values ('e', 'f')) c on 1=1");
        
        expColNames = new String [] {"1", "2"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"e", "f"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        // verifying that oj is not a keyword
        
        rs = st.executeQuery(
            "select * from oj where oj = 1");
        
        expColNames = new String [] {"OJ"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        //verifying both regular and {oj } in
        
        rs = st.executeQuery(
            "select * from t1 left outer join {oj t2 left outer "
            + "join t3 on t2.c1=t3.c1} on t1.c1=t3.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", null, null},
            {"2", null, null},
            {"2", null, null},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        // left and right outer join with an empty table
        
        rs = st.executeQuery(
            "select t1.c1 from t1 left outer join empty_table et "
            + "on t1.c1 = et.c1");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select t1.c1 from t1 right outer join empty_table "
            + "et on t1.c1 = et.c1");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        JDBC.assertDrainResults(rs, 0);
        
        rs = st.executeQuery(
            " select t1.c1 from empty_table et right outer join "
            + "t1 on et.c1 = t1.c1");
        
        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        // this query may make no sense at all, but it's just 
        // trying to show that parser works fine with both regular 
        // tableexpression and tableexpression with {oj }
        
        rs = st.executeQuery(
            "select * from t1, {oj t2 join t3 on t2.c1=t3.c1}");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "3", "3"},
            {"1", "3", "3"},
            {"1", "5", "5"},
            {"1", "5", "5"},
            {"2", "3", "3"},
            {"2", "3", "3"},
            {"2", "5", "5"},
            {"2", "5", "5"},
            {"2", "3", "3"},
            {"2", "3", "3"},
            {"2", "5", "5"},
            {"2", "5", "5"},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"3", "5", "5"},
            {"3", "5", "5"},
            {"4", "3", "3"},
            {"4", "3", "3"},
            {"4", "5", "5"},
            {"4", "5", "5"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        // parameters and join clause
        
        pSt = prepareStatement(
            "select * from t1 left outer join t2 on 1=? and t1.c1 = t2.c1");
        
        rs = st.executeQuery("values 1");
        
        rs.next();
        rsmd = rs.getMetaData();
        for (int i = 1; i <= rsmd.getColumnCount(); i++)
            pSt.setObject(i, rs.getObject(i));
        
        rs = pSt.executeQuery();
        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1"},
            {"2", null},
            {"2", null},
            {"3", "3"},
            {"3", "3"},
            {"4", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        pSt = prepareStatement(
            "select * from t1 left outer join t2 on t1.c1 = "
            + "t2.c1 and t1.c1 = ?");
        
        rs = st.executeQuery("values 1");
        
        rs.next();
        rsmd = rs.getMetaData();
        for (int i = 1; i <= rsmd.getColumnCount(); i++)
            pSt.setObject(i, rs.getObject(i));
        
        rs = pSt.executeQuery();
        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1"},
            {"2", null},
            {"2", null},
            {"3", null},
            {"4", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);        
        
        // additional predicates outside of the join clause egs of 
        // using {oj --} syntax
        
        rs = st.executeQuery(
            "select * from t1 left outer join t2 on t1.c1 = "
            + "t2.c1 where t1.c1 = 1");
        
        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from {oj t1 left outer join t2 on t1.c1 = "
            + "t2.c1} where t1.c1 = 1");
        
        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from t1 right outer join t2 on t1.c1 = 1 "
            + "where t2.c1 = t1.c1");
        
        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from {oj t1 right outer join t2 on t1.c1 "
            + "= 1} where t2.c1 = t1.c1");
        
        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }
    
    public void testOuterJoinWithSubquery() throws Exception
    {
        // subquery in join clause. egs of using {oj --} syntax
        
        ResultSet rs = null;
        Statement st = createStatement();
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);


        rs = st.executeQuery(
            "select * from t1 a left outer join t2 b "
            + "on a.c1 = b.c1 and a.c1 = (select c1 from t1 where "
            + "a.c1 = t1.c1 and a.c1 = 1)");

        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1"},
            {"2", null},
            {"2", null},
            {"3", null},
            {"4", null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from {oj t1 a left outer join t2 b "
            + "on a.c1 = b.c1 and a.c1 = (select c1 from t1 where "
            + "a.c1 = t1.c1 and a.c1 = 1)}");

        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1"},
            {"2", null},
            {"2", null},
            {"3", null},
            {"4", null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from t1 a left outer join t2 b "
            + "on a.c1 = b.c1 and a.c1 = (select c1 from t1 where "
            + "a.c1 = t1.c1 and a.c1 <> 2)");

        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1"},
            {"2", null},
            {"2", null},
            {"3", "3"},
            {"3", "3"},
            {"4", null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from {oj t1 a left outer join t2 b "
            + "on a.c1 = b.c1 and a.c1 = (select c1 from t1 where "
            + "a.c1 = t1.c1 and a.c1 <> 2)}");

        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1"},
            {"2", null},
            {"2", null},
            {"3", "3"},
            {"3", "3"},
            {"4", null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from t1 a right outer join t2 b "
            + "on a.c1 = b.c1 and a.c1 in (select c1 from t1 where "
            + "a.c1 = t1.c1)");

        expColNames = new String [] {"C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1"},
            {"3", "3"},
            {"3", "3"},
            {null, "5"},
            {null, "6"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);
    }
    
    public void testOuterJoinWithinSubquery() throws Exception
    {
        //outer join in subquery egs of using {oj --} syntax
        
        ResultSet rs = null;
        Statement st = createStatement();
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        rs = st.executeQuery(
            "select * from (t1 a)"
            + "where exists (select * from t1 left outer join t2 "
            + "on t1.c1 = t2.c1)");

        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"4"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from (t1 a)"
            + "where exists (select * from {oj t1 left outer join "
            + "t2 on t1.c1 = t2.c1})");

        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"4"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from (t1 a)"
            + "where exists (select * from t1 left outer join t2 on 1=0)");

        expColNames = new String [] {"C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1"},
            {"2"},
            {"2"},
            {"3"},
            {"4"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testNestedJoins() throws Exception
    {
        // nested joins egs of using {oj --} syntax

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);
        
        rs = st.executeQuery(
            "select * from t1 left outer join t2 on t1.c1 = "
            + "t2.c1 left outer join t3 on t1.c1 = t3.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"2", null, "2"},
            {"2", null, "2"},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from {oj t1 left outer join t2 on t1.c1 = "
            + "t2.c1 left outer join t3 on t1.c1 = "
            + "t3.c1}");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"2", null, "2"},
            {"2", null, "2"},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from t1 left outer join t2 on t1.c1 = "
            + "t2.c1 left outer join t3 on t2.c1 = t3.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"2", null, null},
            {"2", null, null},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from t3 right outer join t2 on t3.c1 = "
            + "t2.c1 right outer join t1 on t1.c1 = t2.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {null, "1", "1"},
            {null, null, "2"},
            {null, null, "2"},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {null, null, "4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        // parents
        
        rs = st.executeQuery(
            "select * from (t1 left outer join t2 on t1.c1 = "
            + "t2.c1) left outer join t3 on t1.c1 = t3.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"2", null, "2"},
            {"2", null, "2"},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from t1 left outer join (t2 left outer "
            + "join t3 on t2.c1 = t3.c1) on t1.c1 = t2.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"2", null, null},
            {"2", null, null},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testLeftRightOuterJoinCombination() throws Exception
    {
        // left/right outer join combinations

        ResultSet rs = null;
        Statement st = createStatement();
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);
             
        rs = st.executeQuery(
            "select * from t1 a right outer join t2 b on a.c1 = "
            + "b.c1 left outer join t3 c on a.c1 = b.c1 and b.c1 = c.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {null, "5", null},
            {null, "6", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from (t1 a right outer join t2 b on a.c1 "
            + "= b.c1) left outer join t3 c on a.c1 = b.c1 and b.c1 = c.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {null, "5", null},
            {null, "6", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from t1 a left outer join t2 b on a.c1 = "
            + "b.c1 right outer join t3 c on c.c1 = a.c1 where "
            + "a.c1 is not null");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"2", null, "2"},
            {"2", null, "2"},
            {"3", "3", "3"},
            {"3", "3", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from (t1 a left outer join t2 b on a.c1 = "
            + "b.c1) right outer join t3 c on c.c1 = a.c1 where "
            + "a.c1 is not null");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"2", null, "2"},
            {"2", null, "2"},
            {"3", "3", "3"},
            {"3", "3", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from t1 a left outer join (t2 b right "
            + "outer join t3 c on c.c1 = b.c1) on a.c1 = c.c1 "
            + "where c.c1=b.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "3", "3"},
            {"3", "3", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

    }

    public void testOuterJoinWithInsertUpdateDelete() throws Exception
    {
        // test insert/update/delete

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);       
        
        rs = st.executeQuery("select * from insert_test");
        
        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", "2"},
            {"1", "1", "3"},
            {"1", "1", "5"},
            {"1", "1", "5"},
            {"1", "1", "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"4", null, "2"},
            {"4", null, "3"},
            {"4", null, "5"},
            {"4", null, "5"},
            {"4", null, "7"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        assertUpdateCount(st, 5,
            " update insert_test"
            + " set c1 = (select 9 from t1 a left outer join t1 b"
            + " on a.c1 = b.c1 where a.c1 = 1)"
            + " where c1 = 1");

        rs = st.executeQuery("select * from insert_test");

        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"9", "1", "2"},
            {"9", "1", "3"},
            {"9", "1", "5"},
            {"9", "1", "5"},
            {"9", "1", "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"4", null, "2"},
            {"4", null, "3"},
            {"4", null, "5"},
            {"4", null, "5"},
            {"4", null, "7"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);
        
        assertUpdateCount(st, 5,
            " delete from insert_test"
            + " where c1 = (select 9 from t1 a left outer join t1 b"
            + " on a.c1 = b.c1 where a.c1 = 1)");

        rs = st.executeQuery("select * from insert_test");

        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"4", null, "2"},
            {"4", null, "3"},
            {"4", null, "5"},
            {"4", null, "5"},
            {"4", null, "7"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        assertUpdateCount(st, 21, "delete from insert_test");
        
        st.executeUpdate(
            " insert into insert_test"
            + " (select * from (select * from t1 a left outer join"
            + " t2 b on a.c1 = b.c1 left outer join t3 c on a.c1 <>"
            + " c.c1) d (c1, c2, c3))");
        
        rs = st.executeQuery("select * from insert_test");
        
        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", "2"},
            {"1", "1", "3"},
            {"1", "1", "5"},
            {"1", "1", "5"},
            {"1", "1", "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"2", null, "3"},
            {"2", null, "5"},
            {"2", null, "5"},
            {"2", null, "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"3", "3", "2"},
            {"3", "3", "5"},
            {"3", "3", "5"},
            {"3", "3", "7"},
            {"4", null, "2"},
            {"4", null, "3"},
            {"4", null, "5"},
            {"4", null, "5"},
            {"4", null, "7"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        assertUpdateCount(st, 26, "delete from insert_test");
    }

    public void testMulticolumn() throws Exception
    {
        // multicolumn tests c1, c2, and c3 all have the same values

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        rs = st.executeQuery(
            "select tt1.c1, tt1.c2, tt1.c3, tt2.c2, tt2.c3 from "
            + "tt1 left outer join tt2 on tt1.c1 = tt2.c1");
        
        expColNames = new String [] {"C1", "C2", "C3", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", "1", "1", "1"},
            {"2", "2", "2", null, null},
            {"2", "2", "2", null, null},
            {"3", "3", "3", "3", "3"},
            {"3", "3", "3", "3", "3"},
            {"4", "4", "4", null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select tt1.c1, tt1.c2, tt1.c3, tt2.c3 from tt1 "
            + "left outer join tt2 on tt1.c1 = tt2.c1");
        
        expColNames = new String [] {"C1", "C2", "C3", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", "1", "1"},
            {"2", "2", "2", null},
            {"2", "2", "2", null},
            {"3", "3", "3", "3"},
            {"3", "3", "3", "3"},
            {"4", "4", "4", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select tt1.c1, tt1.c2, tt1.c3 from tt1 left outer "
            + "join tt2 on tt1.c1 = tt2.c1");
        
        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", "1"},
            {"2", "2", "2"},
            {"2", "2", "2"},
            {"3", "3", "3"},
            {"3", "3", "3"},
            {"4", "4", "4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        // nested outer joins

        rs = st.executeQuery(
            "select tt1.c2, tt1.c1, tt1.c3, tt2.c1, tt2.c3 from "
            + "t1 left outer join tt1 on t1.c1 = tt1.c1 left outer "
            + "join tt2 on tt1.c2 = tt2.c2");

        expColNames = new String [] {"C2", "C1", "C3", "C1", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1", "1", "1", "1"},
            {"2", "2", "2", null, null},
            {"2", "2", "2", null, null},
            {"2", "2", "2", null, null},
            {"2", "2", "2", null, null},
            {"3", "3", "3", "3", "3"},
            {"3", "3", "3", "3", "3"},
            {"4", "4", "4", null, null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);
    }
    
    public void testColumnReordering() throws Exception
    {
        // make sure that column reordering is working correctly
        // when there's an ON clause

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        // qualfied * will return all of the columns of the 
        // qualified table including join columns
        
        rs = st.executeQuery(
            "select x.* from x join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select x.* from x left outer join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3"},
            {"4", "5", "6"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select x.* from x right outer join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3"},
            {null, null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select y.* from x join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C3", "C4", "C5"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select y.* from x left outer join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C3", "C4", "C5"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5"},
            {null, null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select y.* from x right outer join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C3", "C4", "C5"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5"},
            {"666", "7", "8"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        // * will return all of the columns of all joined tables
        
        rs = st.executeQuery(
            "select * from x join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C3", "C4", "C5"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3", "3", "4", "5"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from x left outer join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C3", "C4", "C5"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3", "3", "4", "5"},
            {"4", "5", "6", null, null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select * from x right outer join y on x.c3 = y.c3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C3", "C4", "C5"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3", "3", "4", "5"},
            {null, null, null, "666", "7", "8"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testRightOuterJoinXform() throws Exception
    {
        // verify that right outer join xforms don't get result
        // columns confused

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        rs = st.executeQuery(
            " select * from a left outer join b on 1=1 left "
            + "outer join c on 1=1");

        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "3.3", "asdf"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from a left outer join b on 1=1 left "
            + "outer join c on 1=0");

        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "3.3", null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from a left outer join b on 1=0 left "
            + "outer join c on 1=1");

        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", null, "asdf"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from a left outer join b on 1=0 left "
            + "outer join c on 1=0");

        expColNames = new String [] {"C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", null, null}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from c right outer join b on 1=1 right "
            + "outer join a on 1=1");

        expColNames = new String [] {"C3", "C2", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"asdf", "3.3", "1"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from c right outer join b on 1=1 right "
            + "outer join a on 1=0");

        expColNames = new String [] {"C3", "C2", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {null, null, "1"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from c right outer join b on 1=0 right "
            + "outer join a on 1=1");

        expColNames = new String [] {"C3", "C2", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {null, "3.3", "1"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select * from c right outer join b on 1=0 right "
            + "outer join a on 1=0");

        expColNames = new String [] {"C3", "C2", "C1"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {null, null, "1"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);
    }
    
    public void testInnerJoinXform() throws Exception
    {
        // test outer join -> inner join xform
        
        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        assertUpdateCount(st, 5, "delete from tt1");
        assertUpdateCount(st, 5, " delete from tt2");
        assertUpdateCount(st, 5, " delete from tt3");
        
        st.executeUpdate(
            " insert into tt1 values (1, 2, 3), (2, 3, 4), (3, 4, 5)");
        
        st.executeUpdate(
            " insert into tt2 values (1, 2, 3), (2, 3, 4), (3, 4, 5)");
        
        st.executeUpdate(
            " insert into tt3 values (1, 2, 3), (2, 3, 4), (3, 4, 5)");

        CallableStatement cSt = prepareCall(
                " call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        
        assertUpdateCount(cSt, 0);
        
        // no xform, predicate on outer table
        
        rs = st.executeQuery(
            "select * from tt1 left outer join tt2 on tt1.c1 = "
            + "tt2.c2 where tt1.c1 = 3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5", "2", "3", "4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        // various predicates on inner table
        
        rs = st.executeQuery(
            "select * from tt1 left outer join tt2 on tt1.c1 = "
            + "tt2.c2 where tt2.c2 = 3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5", "2", "3", "4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");

        rs.next();
        
        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();
        
        rs = st.executeQuery(
            " select * from tt1 left outer join tt2 on tt1.c1 = "
            + "tt2.c2 where tt2.c1 + 1= tt2.c2");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"2", "3", "4", "1", "2", "3"},
            {"3", "4", "5", "2", "3", "4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");

        rs.next();
        
        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();

        rs = st.executeQuery(
            " select * from tt1 left outer join tt2 on tt1.c1 = "
            + "tt2.c2 where tt2.c1 + 1= 3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5", "2", "3", "4"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
        
        rs.next();

        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();
        
        rs = st.executeQuery(
            " select * from tt2 right outer join tt1 on tt1.c1 = "
            + "tt2.c2 where tt2.c1 + 1= 3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"2", "3", "4", "3", "4", "5"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
        
        rs.next();

        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();
        
        rs = st.executeQuery(
            " select * from tt1 left outer join tt2 on tt1.c1 = "
            + "tt2.c2 left outer join tt3 on tt2.c2 = tt3.c3 where "
            + "tt3.c3 = 3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5", "2", "3", "4", "1", "2", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
        
        rs.next();

        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();
        
        rs = st.executeQuery(
            " select * from tt1 left outer join tt2 on tt1.c1 = "
            + "tt2.c2 left outer join tt3 on tt2.c2 = tt3.c3 where "
            + "tt2.c2 = 3");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"3", "4", "5", "2", "3", "4", "1", "2", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
        
        rs.next();

        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();
        
        // make sure predicates are null tolerant
        
        rs = st.executeQuery(
            "select * from tt1 left outer join tt2 on tt1.c1 = tt2.c2 "
            + "where char(tt2.c2) is null");
        
        expColNames = new String [] {"C1", "C2", "C3", "C1", "C2", "C3"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "2", "3", null, null, null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        // where java.lang.Integer::toString(tt2.c2) = '2';
        
        rs = st.executeQuery(
            "values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
        
        rs.next();

        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();     
    }

    public void testdDerby2924() throws Exception
    {
        // bug 2924, cross join under an outer join

        Statement st = createStatement();
        ResultSet rs = null;        
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        st.executeUpdate(
            "CREATE TABLE inventory(itemno INT NOT NULL PRIMARY "
            + "KEY, capacity INT)");
        
        st.executeUpdate("INSERT INTO inventory VALUES (1, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (2, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (3, 2)");
        
        st.executeUpdate(
            " CREATE TABLE timeslots (slotno INT NOT NULL PRIMARY KEY)");
        
        st.executeUpdate("INSERT INTO timeslots VALUES(1)");
        st.executeUpdate("INSERT INTO timeslots VALUES(2)");
        
        st.executeUpdate(
            " create table reservations(slotno INT CONSTRAINT "
            + "timeslots_fk REFERENCES timeslots, "
            + "itemno INT CONSTRAINT inventory_fk REFERENCES inventory, "
            + "name VARCHAR(100), resdate DATE)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(1, 1, 'Joe', '2000-04-14')");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(1, 1, 'Fred', '2000-04-13')");
        
        // This query used to cause a null pointer exception
        
        rs = st.executeQuery(
            "select name, resdate "
            + "from (reservations left outer join (inventory join "
            + "timeslots on inventory.itemno = timeslots.slotno)"
            + "on inventory.itemno = reservations.itemno and "
            + "timeslots.slotno = reservations.slotno)"
            + "where resdate = '2000-04-14'");
        
        expColNames = new String [] {"NAME", "RESDATE"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"Joe", "2000-04-14"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testdDerby2923() throws Exception
    {
        // bug 2923, cross join under an outer join

        Statement st = createStatement();
        ResultSet rs = null; 
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        st.executeUpdate(
            "create table inventory(itemno INT NOT NULL PRIMARY "
            + "KEY, capacity INT)");
        
        st.executeUpdate("INSERT into inventory values (1, 4)");
        st.executeUpdate("INSERT into inventory values (2, 2)");
        st.executeUpdate("INSERT into inventory values (3, 2)");
        
        st.executeUpdate(
            " CREATE TABLE timeslots (slotno INT NOT NULL PRIMARY KEY)");
        
        st.executeUpdate("INSERT INTO timeslots VALUES(1)");
        st.executeUpdate("INSERT INTO timeslots VALUES(2)");
        
        st.executeUpdate(
            " create table reservations(slotno INT CONSTRAINT "
            + "timeslots_fk REFERENCES timeslots,"
            + "itemno INT CONSTRAINT inventory_fk REFERENCES inventory,"
            + "name VARCHAR(100))");
        
        st.executeUpdate("INSERT INTO reservations VALUES(1, 1, 'Joe')");
        st.executeUpdate("INSERT INTO reservations VALUES(2, 2, 'Fred')");
        
        // This query used to get incorrect results when name is 
        // null was the 2nd predicate due to a bug in OJ->IJ xform 
        // code.
        
        rs = st.executeQuery(
            "select timeslots.slotno, inventory.itemno, capacity, name"
            + " from inventory left outer join timeslots"
            + " on inventory.capacity = timeslots.slotno"
            + " left outer join reservations"
            + " on timeslots.slotno = reservations.slotno"
            + " where capacity > 3 and name is null");
        
        expColNames = new String [] {"SLOTNO", "ITEMNO", "CAPACITY", "NAME"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {null, "1", "4", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        rs = st.executeQuery(
            " select timeslots.slotno, inventory.itemno, capacity, name"
            + " from inventory left outer join timeslots"
            + " on inventory.capacity = timeslots.slotno"
            + " left outer join reservations"
            + " on timeslots.slotno = reservations.slotno"
            + " where name is null and capacity > 3");
        
        expColNames = new String [] {"SLOTNO", "ITEMNO", "CAPACITY", "NAME"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {null, "1", "4", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testdDerby2930() throws Exception
    {

        // bug 2930, cross join under outer join

        Statement st = createStatement();
        ResultSet rs = null;
        SQLWarning sqlWarn = null;      
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        st.executeUpdate(
            "CREATE TABLE properties ("
            + "	name VARCHAR(50),"
            + "	value VARCHAR(200))");
        
        st.executeUpdate(
            " INSERT INTO properties VALUES ('businessName', "
            + "'Cloud 9 Cafe')");
        
        st.executeUpdate(
            " INSERT INTO properties VALUES "
            + "('lastReservationDate', '2001-12-31')");
        
        st.executeUpdate(
            " CREATE TABLE inventory ("
            + "	itemno INT NOT NULL PRIMARY KEY,"
            + "	capacity INT"
            + ")");
        
        st.executeUpdate("INSERT INTO inventory VALUES (1, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (2, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (3, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (4, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (5, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (6, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (7, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (8, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (9, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (10, 4)");
        
        st.executeUpdate(
            " CREATE TABLE timeslots (slot TIME NOT NULL PRIMARY KEY)");
        
        st.executeUpdate("INSERT INTO timeslots VALUES('17:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('17:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('18:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('18:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('19:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('19:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('20:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('20:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('21:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('21:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('22:00:00')");
        
        st.executeUpdate(
            " CREATE TABLE reservations ("
            + "	itemno INT CONSTRAINT inventory_fk REFERENCES inventory,"
            + "	slot TIME CONSTRAINT timeslots_fk REFERENCES timeslots,"
            + "	resdate DATE NOT NULL,"
            + "	name VARCHAR(100) NOT NULL,"
            + "	quantity INT,"
            + "	CONSTRAINT reservations_u UNIQUE(name, resdate))");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(6, '17:00:00', "
            + "'2000-07-13', 'Williams', 4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(7, '17:00:00', "
            + "'2000-07-13', 'Johnson',  4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(8, '17:00:00', "
            + "'2000-07-13', 'Allen',    3)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(9, '17:00:00', "
            + "'2000-07-13', 'Dexmier',  4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(1, '17:30:00', "
            + "'2000-07-13', 'Gates', 	 2)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(2, '17:30:00', "
            + "'2000-07-13', 'McNealy',  2)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(3, '17:30:00', "
            + "'2000-07-13', 'Hoffman',  1)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(4, '17:30:00', "
            + "'2000-07-13', 'Sippl',    2)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(6, '17:30:00', "
            + "'2000-07-13', 'Yang',     4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(7, '17:30:00', "
            + "'2000-07-13', 'Meyers',   4)");
        
        rs = st.executeQuery(
            " select max(name), max(resdate) from inventory join "
            + "timeslots on inventory.capacity is not null "
            + "left outer join reservations on inventory.itemno = "
            + "reservations.itemno and reservations.slot = timeslots.slot");

        expColNames = new String [] {"1", "2"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"Yang", "2000-07-13"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
           " select max(name), max(resdate) from inventory join "
           + "timeslots on inventory.capacity is not null "
           + "left outer join reservations on inventory.itemno = "
           + "reservations.itemno and reservations.slot = timeslots.slot");

        rs.next();
        // This causes the warning to be generated now.
        if (usingEmbedded())
        {
            sqlWarn = rs.getWarnings();
            assertNotNull("Expected warning but found none", sqlWarn);
            //Warning 01003:Null values were eliminated from the
            //argument of a column function
            assertSQLState("01003", sqlWarn);
        }
        assertEquals("Yang", rs.getString(1));
        assertEquals("2000-07-13", rs.getString(2));

    }

    public void testdDerby2931() throws Exception
    {

        // bug 2931, cross join under outer join

        Statement st = createStatement();
        ResultSet rs = null;       
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        st.executeUpdate(
            "CREATE TABLE properties ("
            + "	name VARCHAR(50),"
            + "	value VARCHAR(200))");
        
        st.executeUpdate(
            " INSERT INTO properties VALUES ('businessName', "
            + "'Cloud 9 Cafe')");
        
        st.executeUpdate(
            " INSERT INTO properties VALUES "
            + "('lastReservationDate', '2001-12-31')");
        
        st.executeUpdate(
            " CREATE TABLE inventory ("
            + "	itemno INT NOT NULL PRIMARY KEY,"
            + "	capacity INT"
            + ")");
        
        st.executeUpdate("INSERT INTO inventory VALUES (1, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (2, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (3, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (4, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (5, 2)");
        st.executeUpdate("INSERT INTO inventory VALUES (6, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (7, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (8, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (9, 4)");
        st.executeUpdate("INSERT INTO inventory VALUES (10, 4)");
        
        st.executeUpdate(
            " CREATE TABLE timeslots (slot TIME NOT NULL PRIMARY KEY)");
        
        st.executeUpdate("INSERT INTO timeslots VALUES('17:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('17:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('18:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('18:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('19:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('19:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('20:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('20:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('21:00:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('21:30:00')");
        st.executeUpdate("INSERT INTO timeslots VALUES('22:00:00')");
        
        st.executeUpdate(
            " CREATE TABLE reservations ("
            + "	itemno INT CONSTRAINT inventory_fk REFERENCES inventory,"
            + "	slot TIME CONSTRAINT timeslots_fk REFERENCES timeslots,"
            + "	resdate DATE NOT NULL,"
            + "	name VARCHAR(100) NOT NULL,"
            + "	quantity INT,"
            + "	CONSTRAINT reservations_u UNIQUE(name, resdate))");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(6, '17:00:00', "
            + "'2000-07-13', 'Williams', 4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(7, '17:00:00', "
            + "'2000-07-13', 'Johnson',  4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(8, '17:00:00', "
            + "'2000-07-13', 'Allen',    3)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(9, '17:00:00', "
            + "'2000-07-13', 'Dexmier',  4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(1, '17:30:00', "
            + "'2000-07-13', 'Gates', 	 2)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(2, '17:30:00', "
            + "'2000-07-13', 'McNealy',  2)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(3, '17:30:00', "
            + "'2000-07-13', 'Hoffman',  1)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(4, '17:30:00', "
            + "'2000-07-13', 'Sippl',    2)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(6, '17:30:00', "
            + "'2000-07-13', 'Yang',     4)");
        
        st.executeUpdate(
            " INSERT INTO reservations VALUES(7, '17:30:00', "
            + "'2000-07-13', 'Meyers',   4)");
        
        // this query should return values from the 'slot' column 
        // (type date) but it seems to be returning integers!
        
        rs = st.executeQuery(
            "select max(timeslots.slot) from inventory inner "
            + "join timeslots on inventory.capacity is not null "
            + "left outer join reservations on inventory.capacity "
            + "= reservations.itemno and reservations.slot = timeslots.slot");
        
        expColNames = new String [] {"1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"22:00:00"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testdDerby2897() throws Exception
    {
        // bug 2897 Push join predicates from where clause to right

        Statement st = createStatement();
        ResultSet rs = null;      
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        st.executeUpdate("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");

        rs = st.executeQuery(
            "select * from t1 inner join t2 on 1=1 left outer "
            + "join t3 on t1.c1 = t3.c1 "
            + "where t1.c1 = t2.c1");
        
        expColNames = new String [] {"C1", "C1", "C1"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", "1", null},
            {"3", "3", "3"},
            {"3", "3", "3"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");

        rs.next();

        if(usingEmbedded()){
            RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rs.getString(1));
            assertTrue(rtsp.usedTableScan());
            assertFalse(rtsp.usedDistinctScan());
        }
        rs.close();
    }

    public void testdDerby5659() throws SQLException
    {
        // Test fix for bug 5659

        ResultSet rs = null;
        Statement st = createStatement();
        String [][] expRS;
        String [] expColNames;
             
        st.executeUpdate(
            "create table xxx (a int not null)");
        
        st.executeUpdate(
            " create table yyy (a int not null)");
        
        st.executeUpdate(
            " insert into xxx values (1)");
        
        rs = st.executeQuery(
            " select * from xxx left join yyy on (xxx.a=yyy.a)");
        
        expColNames = new String [] {"A", "A"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1", null}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
        
        assertStatementError("23502", st,
            " insert into xxx values (null)");
        
        rs = st.executeQuery(
            " select * from xxx");
        
        expColNames = new String [] {"A"};
        JDBC.assertColumnNames(rs, expColNames);
        
        expRS = new String [][]
        {
            {"1"}
        };
        
        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testdDerby5658() throws Exception
    {
        // Defect 5658. Disable querries with ambiguous references.

        Statement st = createStatement();
        ResultSet rs = null;    
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);

        st.executeUpdate(
            "create table ttab1 (a int, b int)");

        st.executeUpdate(
            " insert into ttab1 values (1,1),(2,2)");

        st.executeUpdate(
            " create table ttab2 (c int, d int)");

        st.executeUpdate(
            " insert into ttab2 values (1,1),(2,2)");

        // DERBY-4380: These statements used to raise an error
        // because more than one object table includes column "b".
        // But the scope of the ON clauses makes it clear which
        // table they belong to in each case, so they should not fail.

        rs = st.executeQuery(
            "select cor1.*, cor2.* from ttab1 cor1 left outer "
            + "join ttab2 on (b = d),"
            + "		ttab1 left outer join ttab2 cor2 on (b = d)");

        expColNames = new String [] {"A", "B", "C", "D"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1", "1", "1"},
            {"1", "1", "2", "2"},
            {"2", "2", "1", "1"},
            {"2", "2", "2", "2"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        rs = st.executeQuery(
            " select cor1.*, cor2.* from ttab1 cor1 left outer "
            + "join ttab2 on (b = d),"
            + "		ttab1 left outer join ttab2 cor2 on (b = cor2.d)");

        expColNames = new String [] {"A", "B", "C", "D"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1", "1", "1"},
            {"1", "1", "2", "2"},
            {"2", "2", "1", "1"},
            {"2", "2", "2", "2"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        // This should pass

        rs = st.executeQuery(
            "select cor1.*, cor2.* from ttab1 left outer join "
            + "ttab2 on (b = d), "
            + "		ttab1 cor1 left outer join ttab2 cor2 on (cor1.b = cor2.d)");

        expColNames = new String [] {"A", "B", "C", "D"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1", "1", "1"},
            {"2", "2", "2", "2"},
            {"1", "1", "1", "1"},
            {"2", "2", "2", "2"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);

        // These should fail too

        assertStatementError("42X03", st,
            "select * from ttab1, ttab1 left outer join ttab2 on (a=c)");

        assertStatementError("42X04", st,
            " select * from ttab1 cor1, ttab1 left outer join "
            + "ttab2 on (cor1.a=c)");

        // This should pass

        rs = st.executeQuery(
            "select * from ttab1, ttab1 cor1 left outer join "
            + "ttab2 on (cor1.a=c)");

        expColNames = new String [] {"A", "B", "A", "B", "C", "D"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            {"1", "1", "1", "1", "1", "1"},
            {"2", "2", "1", "1", "1", "1"},
            {"1", "1", "2", "2", "2", "2"},
            {"2", "2", "2", "2", "2", "2"}
        };

        JDBC.assertFullResultSet(rs, expRS, true);
    }

    public void testdDerby5164() throws Exception
    {
        // Test 5164

        Statement st = createStatement();
        createTestObjects(st);
        ResultSet rs=null;
        String [][] expRS;
        String [] expColNames;

        createTestObjects(st);
        
        st.executeUpdate(
            "CREATE TABLE \"APP\".\"GOVT_AGCY\" (\"GVA_ID\" "
            + "NUMERIC(20,0) NOT NULL, \"GVA_ORL_ID\" "
            + "NUMERIC(20,0) NOT NULL, \"GVA_GAC_ID\" NUMERIC(20,0))");
        
        st.executeUpdate(
            " CREATE TABLE \"APP\".\"GEO_STRC_ELMT\" (\"GSE_ID\" "
            + "NUMERIC(20,0) NOT NULL, \"GSE_GSET_ID\" "
            + "NUMERIC(20,0) NOT NULL, \"GSE_GA_ID_PRNT\" "
            + "NUMERIC(20,0) NOT NULL, \"GSE_GA_ID_CHLD\" "
            + "NUMERIC(20,0) NOT NULL)");
        
        st.executeUpdate(
            " CREATE TABLE \"APP\".\"GEO_AREA\" (\"GA_ID\" "
            + "NUMERIC(20,0) NOT NULL, \"GA_GAT_ID\" NUMERIC(20,0) "
            + "NOT NULL, \"GA_NM\" VARCHAR(30) NOT NULL, "
            + "\"GA_ABRV_NM\" VARCHAR(5))");

        st.executeUpdate("CREATE TABLE \"APP\".\"REG\" "
                +"(\"REG_ID\" NUMERIC(20,0) NOT NULL, \"REG_NM\" "
                +"VARCHAR(60) NOT NULL, \"REG_DESC\" VARCHAR(240), "
                +"\"REG_ABRV_NM\" VARCHAR(15), \"REG_CD\" "
                +"NUMERIC(8,0) NOT NULL, \"REG_STRT_DT\" TIMESTAMP NOT NULL, "
                +"\"REG_END_DT\" TIMESTAMP NOT NULL DEFAULT "
                + "'"+Timestamp.valueOf("2009-12-05 01:29:59")+"',"
                +"\"REG_EMPR_LIAB_IND\" CHAR(1) NOT NULL DEFAULT 'N', "
                +"\"REG_PAYR_TAX_SURG_CRTF_IND\" CHAR(1) NOT NULL DEFAULT 'N', "
                +"\"REG_PYT_ID\" NUMERIC(20,0), \"REG_GA_ID\" NUMERIC(20,0) NOT NULL, "
                +"\"REG_GVA_ID\" NUMERIC(20,0) NOT NULL, \"REG_REGT_ID\" NUMERIC(20,0) NOT NULL, "
                +"\"REG_PRNT_ID\" NUMERIC(20,0))");
        
        rs=st.executeQuery("SELECT 1 FROM reg "
            +"JOIN geo_area jrsd ON (jrsd.ga_id = reg.reg_ga_id) "
            +"LEFT OUTER JOIN geo_strc_elmt gse ON (gse.gse_ga_id_chld =reg.reg_ga_id) "
            +"LEFT OUTER JOIN geo_area prnt ON (prnt.ga_id =reg.reg_ga_id) "
            +"JOIN govt_agcy gva ON (reg.reg_gva_id = gva.gva_id)");

       expColNames=new String[]{"1"};
       JDBC.assertColumnNames(rs, expColNames);

       expRS=new String[][]{};
       JDBC.assertFullResultSet(rs, expRS,true);
    }


    /**
     * Test that left outer join reordering works as expected.  This fixture is
     * the first repro mentioned for this issue, DERBY-4471. It checks for
     * correct results, and should fail prior to applying issue patch due to
     * erroneous reordering.
     * <p/>
     * The results asserted in these tests for DERBY-4471 are the same as Derby
     * gives without the reordering enabled and the same as Postgres yields.
     * <p/>
     * Note that the patch also opens up for correct reorderings of some
     * queries in the seen in the original lojreorder.sql test (converted to
     * JUnit as LojReorderTest in this patch) that did <em>not</em> happen
     * earlier, in addition to denying the wrong ones documented in DERBY-4471
     * and tested below. These new reorderings are tested, cf the comments in
     * LojReorderTest. Look for the string 4471.

     */
    public void testDerby_4471a() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();

        s.executeUpdate("create table r(c1 char(1))");
        s.executeUpdate("create table s(c1 char(1), c2 char(1))");
        s.executeUpdate("create table t(c1 char(1))");

        s.executeUpdate("insert into r values 'a'");
        s.executeUpdate("insert into s values ('b', default)");
        s.executeUpdate("insert into t values ('c')");

        ResultSet rs = s.executeQuery(
            "select * from r left outer join (s left outer join t " +
            "                                 on s.c2=t.c1 or s.c2 is null)" +
            "                on r.c1=s.c1");
        JDBC.assertFullResultSet(rs, new String[][]{{"a", null, null, null}});
    }

    /**
     * Test that left outer join reordering works as expected.  The schema here
     * is taken from the another issue which also saw this error;
     * DERBY-4712/DERBY-4736. This fixture checks for correct results. The
     * first query should fail prior to applying issue patch due to erroneous
     * reordering, the second is OK to reorder (and would be reordered also
     * prior to the patch).
     */
    public void testDerby_4471b() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();

        s.executeUpdate("create table t0(x int)");
        s.executeUpdate("create table t1(x int)");
        s.executeUpdate("create table t2(x int)");
        s.executeUpdate("create table t3(x int)");
        s.executeUpdate("create table t4(x int)");
        s.executeUpdate("insert into t4 values(0)");
        s.executeUpdate("insert into t4 values(1)");
        s.executeUpdate("insert into t4 values(2)");
        s.executeUpdate("insert into t4 values(3)");
        s.executeUpdate("create table t5(x int)");
        s.executeUpdate("insert into t5 values(0)");
        s.executeUpdate("insert into t5 values(1)");
        s.executeUpdate("insert into t5 values(2)");
        s.executeUpdate("insert into t5 values(3)");
        s.executeUpdate("insert into t5 values(4)");
        s.executeUpdate("create table t6(x int)");
        s.executeUpdate("insert into t6 values(0)");
        s.executeUpdate("insert into t6 values(1)");
        s.executeUpdate("insert into t6 values(2)");
        s.executeUpdate("insert into t6 values(3)");
        s.executeUpdate("insert into t6 values(4)");
        s.executeUpdate("insert into t6 values(5)");
        s.executeUpdate("create table t7(x int)");
        s.executeUpdate("insert into t7 values(0)");
        s.executeUpdate("insert into t7 values(1)");
        s.executeUpdate("insert into t7 values(2)");
        s.executeUpdate("insert into t7 values(3)");
        s.executeUpdate("insert into t7 values(4)");
        s.executeUpdate("insert into t7 values(5)");
        s.executeUpdate("insert into t7 values(6)");
        s.executeUpdate("create table t8(x int)");
        s.executeUpdate("insert into t8 values(0)");
        s.executeUpdate("insert into t8 values(1)");
        s.executeUpdate("insert into t8 values(2)");
        s.executeUpdate("insert into t8 values(3)");
        s.executeUpdate("insert into t8 values(4)");
        s.executeUpdate("insert into t8 values(5)");
        s.executeUpdate("insert into t8 values(6)");
        s.executeUpdate("insert into t8 values(7)");
        s.executeUpdate("create table t9(x int)");
        s.executeUpdate("insert into t9 values(0)");
        s.executeUpdate("insert into t9 values(1)");
        s.executeUpdate("insert into t9 values(2)");
        s.executeUpdate("insert into t9 values(3)");
        s.executeUpdate("insert into t9 values(4)");
        s.executeUpdate("insert into t9 values(5)");
        s.executeUpdate("insert into t9 values(6)");
        s.executeUpdate("insert into t9 values(7)");
        s.executeUpdate("insert into t9 values(8)");
        s.executeUpdate("insert into t0 values(1)");
        s.executeUpdate("insert into t1 values(2)");
        s.executeUpdate("insert into t0 values(3)");
        s.executeUpdate("insert into t1 values(3)");
        s.executeUpdate("insert into t2 values(4)");
        s.executeUpdate("insert into t0 values(5)");
        s.executeUpdate("insert into t2 values(5)");
        s.executeUpdate("insert into t1 values(6)");
        s.executeUpdate("insert into t2 values(6)");
        s.executeUpdate("insert into t0 values(7)");
        s.executeUpdate("insert into t1 values(7)");
        s.executeUpdate("insert into t2 values(7)");
        s.executeUpdate("insert into t3 values(8)");
        s.executeUpdate("insert into t0 values(9)");
        s.executeUpdate("insert into t3 values(9)");
        s.executeUpdate("insert into t1 values(10)");
        s.executeUpdate("insert into t3 values(10)");
        s.executeUpdate("insert into t0 values(11)");
        s.executeUpdate("insert into t1 values(11)");
        s.executeUpdate("insert into t3 values(11)");
        s.executeUpdate("insert into t2 values(12)");
        s.executeUpdate("insert into t3 values(12)");
        s.executeUpdate("insert into t0 values(13)");
        s.executeUpdate("insert into t2 values(13)");
        s.executeUpdate("insert into t3 values(13)");
        s.executeUpdate("insert into t1 values(14)");
        s.executeUpdate("insert into t2 values(14)");
        s.executeUpdate("insert into t3 values(14)");
        s.executeUpdate("insert into t0 values(15)");
        s.executeUpdate("insert into t1 values(15)");
        s.executeUpdate("insert into t2 values(15)");
        s.executeUpdate("insert into t3 values(15)");

        // The theory exposed in Galindo-Legaria, C. & Rosenthal, A.:
        // "Outerjoin simplification and reordering for query optimization",
        // ACM Transactions on Database Systems, Vol 22, No 1, March 1997 uses
        // two assumption for its general case which involves full outer joins
        // as well: no duplicate rows and no rows consisting of only nulls. We
        // cannot make that assumption, this being SQL, but for our restricted
        // OJ rewrites, this should work ok, so we throw in both into the test
        // mix:

        // Make duplicates
        s.executeUpdate("insert into t2 select * from t2");
        s.executeUpdate("insert into t3 select * from t3");
        s.executeUpdate("insert into t4 select * from t4");

        // Insert full NULL tuples
        s.executeUpdate("insert into t2 values cast(null as int)");
        s.executeUpdate("insert into t3 values cast(null as int)");
        s.executeUpdate("insert into t4 values cast(null as int)");

        // This query was wrong prior to DERBY-4471: 1=1 is not allowed, since
        // the inner join predicate does not reference T3 and T4 as required.
        ResultSet rs = s.executeQuery(
            "SELECT * FROM (T2 LEFT JOIN (T3 left outer JOIN T4 " +
            "                                    ON 1=1) " +
            "                      ON T2.X = T3.X)");

        JDBC.assertUnorderedResultSet(
            rs,
            new String[][] {
                {"4", null, null},
                {"5", null, null},
                {"6", null, null},
                {"7", null, null},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", null},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", null},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", null},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", null},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", null},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", null},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", null},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", null},
                {"4", null, null},
                {"5", null, null},
                {"6", null, null},
                {"7", null, null},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", null},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", "0"},
                {"12", "12", "1"},
                {"12", "12", "2"},
                {"12", "12", "3"},
                {"12", "12", null},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", null},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", "0"},
                {"13", "13", "1"},
                {"13", "13", "2"},
                {"13", "13", "3"},
                {"13", "13", null},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", null},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", "0"},
                {"14", "14", "1"},
                {"14", "14", "2"},
                {"14", "14", "3"},
                {"14", "14", null},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", null},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", "0"},
                {"15", "15", "1"},
                {"15", "15", "2"},
                {"15", "15", "3"},
                {"15", "15", null},
                {null, null, null}});

        s.execute("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");

        // This one *can* be reordered
        rs = s.executeQuery(
            "SELECT * FROM (T2 LEFT JOIN (T3 left outer JOIN T4 " +
            "                                    ON t3.x=t4.x) " +
            "                      ON T2.X = T3.X)");

        JDBC.assertUnorderedResultSet(
            rs,
            new String[][] {
                {"4", null, null},
                {"4", null, null},
                {"5", null, null},
                {"5", null, null},
                {"6", null, null},
                {"6", null, null},
                {"7", null, null},
                {"7", null, null},
                {"12", "12", null},
                {"12", "12", null},
                {"12", "12", null},
                {"12", "12", null},
                {"13", "13", null},
                {"13", "13", null},
                {"13", "13", null},
                {"13", "13", null},
                {"14", "14", null},
                {"14", "14", null},
                {"14", "14", null},
                {"14", "14", null},
                {"15", "15", null},
                {"15", "15", null},
                {"15", "15", null},
                {"15", "15", null},
                {null, null, null}});

        JDBC.checkPlan(s,
                  new String[] {
                      "Hash Left Outer Join ResultSet:",
                      "Left result set:",
                      "_Hash Left Outer Join ResultSet:"});
    }

    /**
     * Check that ordering actually takes place as well as checking that the
     * results are correct.
     */
    public void testDerby_4471c() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();

        s.execute("create table t1(c1 int)");
        s.execute("create table t2(c1 int)");
        s.execute("create table t3(c1 int)");

        s.execute("insert into t1 values 1, 2, 2, 3, 4");
        s.execute("insert into t2 values 1, 3, 3, 5, 6");
        s.execute("insert into t3 values 2, 3, 5, 5, 7");

        s.execute("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");

        PreparedStatement ps = prepareStatement(
            "select * from t1 left outer join " +
            "     (t2 left outer join t3 on t2.c1 = t3.c1)" +
            "   on t1.c1 = t2.c1");

        ResultSet rs = ps.executeQuery();

        JDBC.assertUnorderedResultSet(
            rs,
            new String[][] {
                {"1", "1", null},
                {"2", null, null},
                {"2", null, null},
                {"3", "3", "3"},
                {"3", "3", "3"},
                {"4", null, null}});

        JDBC.checkPlan(s,
                  new String[] {
                      "Hash Left Outer Join ResultSet:",
                      "Left result set:",
                      "_Hash Left Outer Join ResultSet:"});
    }

    /**
     * Check that ordering actually takes place (more complex: nested
     * reordering) as well as checking that the results are correct.
     */
    public void testDerby_4471d() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();

        s.execute("create table t1(c1 int)");
        s.execute("create table t2(c2 int)");
        s.execute("create table t3(c3 int)");

        s.execute("insert into t1 values 1, 2, 2, 3, 4");
        s.execute("insert into t2 values 1, 3, 3, 5, 6");
        s.execute("insert into t3 values 2, 3, 5, 5, 7");

        s.execute("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");



        PreparedStatement ps = prepareStatement(
            "select * from t1 t1_o left outer join " +
            "     ((t1 left outer join t2 on t1.c1 = t2.c2) left outer join " +
            "      t3 on t2.c2 = t3.c3)" +
            "   on t1_o.c1 = t2.c2");

        // Expect one reordering, cf. rtsp.assertSequence below:
        //
        //     LOJ1 [t1_o.c1 = t2.c2]               LOJ1 [t2.c2=t3.c3]
        //     /  \                                  /   \
        //    /    \                                /    t3
        //   /      \                             LOJ2 [t1_o.c1 = t2.c2]
        //  t1_o   LOJ2 [t2.c2=t3.c3]            /    \
        //          /   \                       /     LOJ3 [t2.c2=t3.c3]
        //         /     \             =>    t1_o     /  \
        //        /       t3                         /    \
        //       /                                  t1    t2
        //      LOJ3 [t1.c1=t2.c2]
        //      /  \
        //     /    \
        //   t1     t2
        //
        // The reason we don't get two reorderings here is that the predicate
        // on LOJ1, "t1_o.c1 = t2.c2" refrences LOJ3's null producing side,
        // t2. Contrast with next example below.

        ResultSet rs = ps.executeQuery();

        JDBC.assertUnorderedResultSet(
            rs,
            new String[][] {
                {"1", "1", "1", null},
                {"2", null, null, null},
                {"2", null, null, null},
                {"3", "3", "3", "3"},
                {"3", "3", "3", "3"},
                {"4", null, null, null}});

        JDBC.checkPlan(s,
                  new String[] {
                      "Hash Left Outer Join ResultSet:",
                      "Left result set:",
                      "_Nested Loop Left Outer Join ResultSet:",
                      "_Left result set:",
                      "_Right result set:",
                      "__Source result set:",
                      "___Hash Left Outer Join ResultSet:"});

        ps = prepareStatement(
            "select * from " +
            "    t1 t1_o left outer join " +
            "        ((t1 t1_i left outer join t2 " +
            "          on t1_i.c1 = t2.c2) left outer join t3 " +
            "         on t1_i.c1 = t3.c3)" +
            "    on t1_o.c1 = t1_i.c1");

        // Expect two reorderings, cf. rtsp.assertSequence below:
        //
        //      LOJ1 [t1_o.c1 = t1_i.c1]               LOJ1 [t2.c2=t3.c3]
        //      /  \                                  /   \
        //     /    \                                /    t3
        //    /      \                             LOJ2 [t1_o.c1 = t1_i.c1]
        //   t1_o   LOJ2 [t1_i.c1=t3.c3]          /    \
        //           /   \                       /     LOJ3 [t1.c1=t2.c2]
        //          /     \             =>    t1_o     /  \
        //         /       t3                         /    \
        //       LOJ3 [t1_i.c1=t2.c2]               t1_i   t2
        //       /  \
        //      /    \
        //    t1_i    t2                =>
        //                                          LOJ1 [t2.c2=t3.c3]
        //                                           /   \
        //                                          /    t3
        //                                       LOJ2 [t2.c2=t3.c3]
        //                                        /  \
        //                                       /    t2
        //                                     LOJ3  [t1_o.c1 = t1_i.c1]
        //                                     /   \
        //                                   t1_o  t1_i
        //
        // In this example, LOJ1's predicate references LOJ3's row preserving
        // side (t1_i), so we get two reorderings.

        rs = ps.executeQuery();

        JDBC.assertUnorderedResultSet(
            rs,
            new String[][] {
                {"1", "1", "1", null},
                {"2", "2", null, "2"},
                {"2", "2", null, "2"},
                {"2", "2", null, "2"},
                {"2", "2", null, "2"},
                {"3", "3", "3", "3"},
                {"3", "3", "3", "3"},
                {"4", "4", null, null}});

        JDBC.checkPlan(s,
                  new String[] {
                      "Hash Left Outer Join ResultSet:",
                      "Left result set:",
                      "_Hash Left Outer Join ResultSet:",
                      "_Left result set:",
                      "__Hash Left Outer Join ResultSet:"});
    }


   /**
    * This fixture would give:
    * <pre>
    *   ASSERT FAILED sourceResultSetNumber expected to be >= 0 for T2.X
    * </pre>
    * error in sane mode prior to DERBY-4736 due to a missing rebinding
    * operation as a result a the LOJ reordering.  Schema and query originally
    * stems from DERBY-4712 (parent issue of DERBY-4736).
    */
    public void testDerby_4736() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();

        s.executeUpdate("create table t0(x int)");
        s.executeUpdate("create table t1(x int)");
        s.executeUpdate("create table t2(x int)");
        s.executeUpdate("create table t3(x int)");
        s.executeUpdate("create table t4(x int)");
        s.executeUpdate("insert into t4 values(0)");
        s.executeUpdate("insert into t4 values(1)");
        s.executeUpdate("insert into t4 values(2)");
        s.executeUpdate("insert into t4 values(3)");
        s.executeUpdate("create table t5(x int)");
        s.executeUpdate("insert into t5 values(0)");
        s.executeUpdate("insert into t5 values(1)");
        s.executeUpdate("insert into t5 values(2)");
        s.executeUpdate("insert into t5 values(3)");
        s.executeUpdate("insert into t5 values(4)");
        s.executeUpdate("create table t6(x int)");
        s.executeUpdate("insert into t6 values(0)");
        s.executeUpdate("insert into t6 values(1)");
        s.executeUpdate("insert into t6 values(2)");
        s.executeUpdate("insert into t6 values(3)");
        s.executeUpdate("insert into t6 values(4)");
        s.executeUpdate("insert into t6 values(5)");
        s.executeUpdate("create table t7(x int)");
        s.executeUpdate("insert into t7 values(0)");
        s.executeUpdate("insert into t7 values(1)");
        s.executeUpdate("insert into t7 values(2)");
        s.executeUpdate("insert into t7 values(3)");
        s.executeUpdate("insert into t7 values(4)");
        s.executeUpdate("insert into t7 values(5)");
        s.executeUpdate("insert into t7 values(6)");
        s.executeUpdate("create table t8(x int)");
        s.executeUpdate("insert into t8 values(0)");
        s.executeUpdate("insert into t8 values(1)");
        s.executeUpdate("insert into t8 values(2)");
        s.executeUpdate("insert into t8 values(3)");
        s.executeUpdate("insert into t8 values(4)");
        s.executeUpdate("insert into t8 values(5)");
        s.executeUpdate("insert into t8 values(6)");
        s.executeUpdate("insert into t8 values(7)");
        s.executeUpdate("create table t9(x int)");
        s.executeUpdate("insert into t9 values(0)");
        s.executeUpdate("insert into t9 values(1)");
        s.executeUpdate("insert into t9 values(2)");
        s.executeUpdate("insert into t9 values(3)");
        s.executeUpdate("insert into t9 values(4)");
        s.executeUpdate("insert into t9 values(5)");
        s.executeUpdate("insert into t9 values(6)");
        s.executeUpdate("insert into t9 values(7)");
        s.executeUpdate("insert into t9 values(8)");
        s.executeUpdate("insert into t0 values(1)");
        s.executeUpdate("insert into t1 values(2)");
        s.executeUpdate("insert into t0 values(3)");
        s.executeUpdate("insert into t1 values(3)");
        s.executeUpdate("insert into t2 values(4)");
        s.executeUpdate("insert into t0 values(5)");
        s.executeUpdate("insert into t2 values(5)");
        s.executeUpdate("insert into t1 values(6)");
        s.executeUpdate("insert into t2 values(6)");
        s.executeUpdate("insert into t0 values(7)");
        s.executeUpdate("insert into t1 values(7)");
        s.executeUpdate("insert into t2 values(7)");
        s.executeUpdate("insert into t3 values(8)");
        s.executeUpdate("insert into t0 values(9)");
        s.executeUpdate("insert into t3 values(9)");
        s.executeUpdate("insert into t1 values(10)");
        s.executeUpdate("insert into t3 values(10)");
        s.executeUpdate("insert into t0 values(11)");
        s.executeUpdate("insert into t1 values(11)");
        s.executeUpdate("insert into t3 values(11)");
        s.executeUpdate("insert into t2 values(12)");
        s.executeUpdate("insert into t3 values(12)");
        s.executeUpdate("insert into t0 values(13)");
        s.executeUpdate("insert into t2 values(13)");
        s.executeUpdate("insert into t3 values(13)");
        s.executeUpdate("insert into t1 values(14)");
        s.executeUpdate("insert into t2 values(14)");
        s.executeUpdate("insert into t3 values(14)");
        s.executeUpdate("insert into t0 values(15)");
        s.executeUpdate("insert into t1 values(15)");
        s.executeUpdate("insert into t2 values(15)");
        s.executeUpdate("insert into t3 values(15)");

        s.execute("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");

        ResultSet rs = s.executeQuery(
            "select t0.x , t1.x , t2.x , t3.x , t4.x , t5.x , t6.x from " +
            "    ((t0 right outer join " +
            "         (t1 right outer join " +
            //            t2 LOJ (t3 LOJ t4) will be reordered
            "             (t2 left outer join " +
            "                 (t3 left outer join t4 on t3.x = t4.x ) " +
            "              on t2.x = t3.x ) " +
            "          on t1.x = t3.x ) " +
            "      on t0.x = t1.x ) " +
            "     left outer join " +
            "      (t5 inner join t6 on t5.x = t6.x ) " +
            "     on t2.x = t5.x)" );

        // The expected result below has been verified to the one we get if we
        // don't reorder LOJ.
        JDBC.assertUnorderedResultSet(
            rs,
            new String[][] {
                {null, null, "4", null, null, "4", "4"},
                {null, null, "5", null, null, null, null},
                {null, null, "6", null, null, null, null},
                {null, null, "7", null, null, null, null},
                {null, null, "12", "12", null, null, null},
                {null, null, "13", "13", null, null, null},
                {null, "14", "14", "14", null, null, null},
                {"15", "15", "15", "15", null, null, null}});

        rs = s.executeQuery("values SYSCS_UTIL.SYSCS_GET_RUNTIMESTATISTICS()");
        rs.next();
        String rts = rs.getString(1);

        // Now verify that we actually *did* reorder
        RuntimeStatisticsParser rtsp = new RuntimeStatisticsParser(rts);
        rtsp.assertSequence(
            new String[] {
                "_Nested Loop Left Outer Join ResultSet:",
                "_Left result set:",
                "__Hash Left Outer Join ResultSet:",
                "__Left result set:",
                "___Hash Left Outer Join ResultSet:",
                "___Left result set:",
                "____Hash Left Outer Join ResultSet:",
                "____Left result set:",
                "_____Hash Left Outer Join ResultSet:",
                // Note: T2 and T3 are in innermost LOJ as expected
                // whereas originally it was T3 and T4
                "_____Left result set:",
                "______Table Scan ResultSet for T2 ",
                "_____Right result set:",
                "______Hash Scan ResultSet for T3 ",
                "____Right result set:",
                "_____Hash Scan ResultSet for T4"});

        rs.close();
    }


    /**
     * Test for a follow-up patch for DERBY-4736: verify that nullability in
     * result set metadata is correct also for columns for the null-producing
     * side of the LOJ.
     */
    public void testDerby_4736_nullability() throws Exception
    {
        setAutoCommit(false);

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;
        String [] expColNames;

        st.executeUpdate(
            "CREATE TABLE T (A INT NOT NULL, B DECIMAL(10,3) NOT "
            + "NULL, C VARCHAR(5) NOT NULL)");

        st.executeUpdate(
            " INSERT INTO T VALUES (1, 1.0, '1'), (2, 2.0, '2'), "
            + "(3, 3.0, '3')");

        st.executeUpdate(
            " CREATE TABLE S (D INT NOT NULL, E DECIMAL(10,3) "
            + "NOT NULL, F VARCHAR(5) NOT NULL)");

        st.executeUpdate(
            " INSERT INTO S VALUES (2, 2.0, '2'), (3, 3.0, '3'), "
            + "(4, 4.0, '4')");

        st.executeUpdate(
            "create view v1 (fv, ev, dv, cv, bv, av) as (select "
            + "f, e, d, c, b, a from t left outer join s on b = e)");

        rs = st.executeQuery(
            " select * from t left outer join (s left outer join "
            + "v1 on (f = cv)) on (d=a)");

        expColNames = new String [] {"A", "B", "C", "D", "E", "F",
                                     "FV", "EV", "DV", "CV", "BV", "AV"};
        JDBC.assertColumnNames(rs, expColNames);

        expRS = new String [][]
        {
            // Before the follow-up patch, the three first NULL column values
            // below would get NOT NULL metadata before the follow-up patch
            // (caught by JDBC.assertResultColumnNullable called from
            // JDBC.assertRowInResultSet if a null value is seen).
            //
            {"1", "1.000", "1", null, null, null,
             "1", "1.000", "1", null, null, null},

            {"2", "2.000", "2", "2", "2.000", "2",
             "2", "2.000", "2", "2", "2.000", "2"},

            {"3", "3.000", "3", "3", "3.000", "3",
             "3", "3.000", "3", "3", "3.000", "3"}
        };

        JDBC.assertFullResultSet(rs, expRS);
    }


    /**
     * Test the queries reported in DERBY-4712 as giving null pointer
     * exceptions. Should fail with NPE before the fix went in.  For bug
     * explanation, see the JIRA issue and {@code JoinNode#transformOuterJoins}.
     */
    public void testDerby_4712_NPEs() throws Exception
    {
        setAutoCommit(false);

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;

        st.executeUpdate("create table t0(x0 int)");
        st.executeUpdate("create table t1(x1 int)");
        st.executeUpdate("create table t2(x2 int)");
        st.executeUpdate("create table t3(x3 int)");
        st.executeUpdate("create table t4(x4 int)");
        st.executeUpdate("insert into t4 values(0)");
        st.executeUpdate("insert into t4 values(1)");
        st.executeUpdate("insert into t4 values(2)");
        st.executeUpdate("insert into t4 values(3)");
        st.executeUpdate("create table t5(x5 int)");
        st.executeUpdate("insert into t5 values(0)");
        st.executeUpdate("insert into t5 values(1)");
        st.executeUpdate("insert into t5 values(2)");
        st.executeUpdate("insert into t5 values(3)");
        st.executeUpdate("insert into t5 values(4)");
        st.executeUpdate("create table t6(x6 int)");
        st.executeUpdate("insert into t6 values(0)");
        st.executeUpdate("insert into t6 values(1)");
        st.executeUpdate("insert into t6 values(2)");
        st.executeUpdate("insert into t6 values(3)");
        st.executeUpdate("insert into t6 values(4)");
        st.executeUpdate("insert into t6 values(5)");
        st.executeUpdate("create table t7(x7 int)");
        st.executeUpdate("insert into t7 values(0)");
        st.executeUpdate("insert into t7 values(1)");
        st.executeUpdate("insert into t7 values(2)");
        st.executeUpdate("insert into t7 values(3)");
        st.executeUpdate("insert into t7 values(4)");
        st.executeUpdate("insert into t7 values(5)");
        st.executeUpdate("insert into t7 values(6)");
        st.executeUpdate("create table t8(x8 int)");
        st.executeUpdate("insert into t8 values(0)");
        st.executeUpdate("insert into t8 values(1)");
        st.executeUpdate("insert into t8 values(2)");
        st.executeUpdate("insert into t8 values(3)");
        st.executeUpdate("insert into t8 values(4)");
        st.executeUpdate("insert into t8 values(5)");
        st.executeUpdate("insert into t8 values(6)");
        st.executeUpdate("insert into t8 values(7)");
        st.executeUpdate("create table t9(x9 int)");
        st.executeUpdate("insert into t9 values(0)");
        st.executeUpdate("insert into t9 values(1)");
        st.executeUpdate("insert into t9 values(2)");
        st.executeUpdate("insert into t9 values(3)");
        st.executeUpdate("insert into t9 values(4)");
        st.executeUpdate("insert into t9 values(5)");
        st.executeUpdate("insert into t9 values(6)");
        st.executeUpdate("insert into t9 values(7)");
        st.executeUpdate("insert into t9 values(8)");
        st.executeUpdate("insert into t0 values(1)");
        st.executeUpdate("insert into t1 values(2)");
        st.executeUpdate("insert into t0 values(3)");
        st.executeUpdate("insert into t1 values(3)");
        st.executeUpdate("insert into t2 values(4)");
        st.executeUpdate("insert into t0 values(5)");
        st.executeUpdate("insert into t2 values(5)");
        st.executeUpdate("insert into t1 values(6)");
        st.executeUpdate("insert into t2 values(6)");
        st.executeUpdate("insert into t0 values(7)");
        st.executeUpdate("insert into t1 values(7)");
        st.executeUpdate("insert into t2 values(7)");
        st.executeUpdate("insert into t3 values(8)");
        st.executeUpdate("insert into t0 values(9)");
        st.executeUpdate("insert into t3 values(9)");
        st.executeUpdate("insert into t1 values(10)");
        st.executeUpdate("insert into t3 values(10)");
        st.executeUpdate("insert into t0 values(11)");
        st.executeUpdate("insert into t1 values(11)");
        st.executeUpdate("insert into t3 values(11)");
        st.executeUpdate("insert into t2 values(12)");
        st.executeUpdate("insert into t3 values(12)");
        st.executeUpdate("insert into t0 values(13)");
        st.executeUpdate("insert into t2 values(13)");
        st.executeUpdate("insert into t3 values(13)");
        st.executeUpdate("insert into t1 values(14)");
        st.executeUpdate("insert into t2 values(14)");
        st.executeUpdate("insert into t3 values(14)");
        st.executeUpdate("insert into t0 values(15)");
        st.executeUpdate("insert into t1 values(15)");
        st.executeUpdate("insert into t2 values(15)");
        st.executeUpdate("insert into t3 values(15)");

        rs = st.executeQuery(
        "SELECT t0.x0,                                                  " +
        "       t1.x1,                                                  " +
        "       t2.x2,                                                  " +
        "       t3.x3,                                                  " +
        "       t4.x4,                                                  " +
        "       t5.x5,                                                  " +
        "       t6.x6,                                                  " +
        "       t7.x7,                                                  " +
        "       t8.x8                                                   " +
        "FROM   (((t0                                                   " +
        "          INNER JOIN ((t1                                      " +
        "                       RIGHT OUTER JOIN (t2                    " +
        "                                         INNER JOIN t3         " +
        "                                           ON t2.x2 = t3.x3 )  " +
        "                         ON t1.x1 = t2.x2 )                    " +
        "                      LEFT OUTER JOIN (t4                      " +
        "                                       INNER JOIN t5           " +
        "                                         ON t4.x4 = t5.x5 )    " +
        "                        ON t1.x1 = t4.x4 )                     " +
        "            ON t0.x0 = t2.x2 )                                 " +
        "         LEFT OUTER JOIN (t6                                   " +
        "                          INNER JOIN t7                        " +
        "                            ON t6.x6 = t7.x7 )                 " +
        "           ON t1.x1 = t6.x6 )                                  " +
        "        INNER JOIN t8                                          " +
        "          ON t5.x5 = t8.x8 )                                   ");

        JDBC.assertEmpty(rs);

        rs = st.executeQuery(
        "SELECT t0.x0,                                               " +
        "       t1.x1,                                               " +
        "       t2.x2,                                               " +
        "       t3.x3,                                               " +
        "       t4.x4,                                               " +
        "       t5.x5,                                               " +
        "       t6.x6,                                               " +
        "       t7.x7                                                " +
        "FROM   ((t0                                                 " +
        "         RIGHT OUTER JOIN t1                                " +
        "           ON t0.x0 = t1.x1 )                               " +
        "        INNER JOIN (((t2                                    " +
        "                      INNER JOIN (t3                        " +
        "                                  LEFT OUTER JOIN t4        " +
        "                                    ON t3.x3 = t4.x4 )      " +
        "                        ON t2.x2 = t3.x3 )                  " +
        "                     RIGHT OUTER JOIN t5                    " +
        "                       ON t2.x2 = t5.x5 )                   " +
        "                    LEFT OUTER JOIN (t6                     " +
        "                                     INNER JOIN t7          " +
        "                                       ON t6.x6 = t7.x7 )   " +
        "                      ON t4.x4 = t6.x6 )                    " +
        "          ON t0.x0 = t5.x5 )                                ");

        expRS = new String [][]
        {
            {"3", "3", null, null, null, "3", null, null}
        };

        JDBC.assertFullResultSet(rs, expRS);

        rs = st.executeQuery(
        "SELECT t0.x0,                                                " +
        "       t1.x1,                                                " +
        "       t2.x2,                                                " +
        "       t3.x3,                                                " +
        "       t4.x4,                                                " +
        "       t5.x5,                                                " +
        "       t6.x6,                                                " +
        "       t7.x7                                                 " +
        "FROM   ((((t0                                                " +
        "           LEFT OUTER JOIN t1                                " +
        "             ON t0.x0 = t1.x1 )                              " +
        "          RIGHT OUTER JOIN t2                                " +
        "            ON t0.x0 = t2.x2 )                               " +
        "         RIGHT OUTER JOIN t3                                 " +
        "           ON t0.x0 = t3.x3 )                                " +
        "        INNER JOIN ((t4                                      " +
        "                     INNER JOIN t5                           " +
        "                       ON t4.x4 = t5.x5 )                    " +
        "                    RIGHT OUTER JOIN (t6                     " +
        "                                      RIGHT OUTER JOIN t7    " +
        "                                        ON t6.x6 = t7.x7 )   " +
        "                      ON t4.x4 = t6.x6 )                     " +
        "          ON t1.x1 = t4.x4 )                                 ");

        JDBC.assertEmpty(rs);

        rs = st.executeQuery(
        "SELECT t0.x0,                                    " +
        "       t1.x1,                                    " +
        "       t2.x2,                                    " +
        "       t3.x3,                                    " +
        "       t4.x4,                                    " +
        "       t5.x5                                     " +
        "FROM   (((t0                                     " +
        "          INNER JOIN t1                          " +
        "            ON t0.x0 = t1.x1 )                   " +
        "         RIGHT OUTER JOIN (t2                    " +
        "                           RIGHT OUTER JOIN t3   " +
        "                             ON t2.x2 = t3.x3 )  " +
        "           ON t0.x0 = t2.x2 )                    " +
        "        INNER JOIN (t4                           " +
        "                    LEFT OUTER JOIN t5           " +
        "                      ON t4.x4 = t5.x5 )         " +
        "          ON t1.x1 = t4.x4 )                     ");

        JDBC.assertEmpty(rs);

        rs = st.executeQuery(
        "SELECT t0.x0,                                                    " +
        "       t1.x1,                                                    " +
        "       t2.x2,                                                    " +
        "       t3.x3,                                                    " +
        "       t4.x4,                                                    " +
        "       t5.x5,                                                    " +
        "       t6.x6                                                     " +
        "FROM   ((t0                                                      " +
        "         RIGHT OUTER JOIN                                        " +
        "                  (t1                                            " +
        "                   RIGHT OUTER JOIN (t2                          " +
        "                                     LEFT OUTER JOIN             " +
        "                                           (t3                   " +
        "                                            LEFT OUTER JOIN t4   " +
        "                                               ON t3.x3 = t4.x4  " +
        "                                            )                    " +
        "                                               ON t2.x2 = t3.x3 )" +
        "                      ON t1.x1 = t3.x3 )                         " +
        "           ON t0.x0 = t1.x1 )                                    " +
        "        LEFT OUTER JOIN (t5                                      " +
        "                         INNER JOIN t6                           " +
        "                           ON t5.x5 = t6.x6 )                    " +
        "          ON t2.x2 = t5.x5 )                                     ");

        expRS = new String [][]
        {
            {null, null, "4", null, null, "4", "4"},
            {null, null, "5", null, null, null, null},
            {null, null, "6", null, null, null, null},
            {null, null, "7", null, null, null, null},
            {null, null, "12", "12", null, null, null},
            {null, null, "13", "13", null, null, null},
            {null, "14", "14", "14", null, null, null},
            {"15", "15", "15", "15", null, null, null},
        };

        JDBC.assertFullResultSet(rs, expRS);
    }


    /**
     * Test the queries reported in DERBY-4798 as giving null pointer
     * exceptions. Should fail with NPE before the fix went in.
     */
    public void testDerby_4798_NPE() throws Exception
    {
        setAutoCommit(false);

        Statement st = createStatement();
        ResultSet rs = null;
        String [][] expRS;

        st.executeUpdate("create table t0(x0 int)");
        st.executeUpdate("create table t1(x1 int)");
        st.executeUpdate("create table t2(x2 int)");
        st.executeUpdate("create table t3(x3 int)");
        st.executeUpdate("create table t4(x4 int)");
        st.executeUpdate("insert into t4 values(0)");
        st.executeUpdate("insert into t4 values(1)");
        st.executeUpdate("insert into t4 values(2)");
        st.executeUpdate("insert into t4 values(3)");
        st.executeUpdate("create table t5(x5 int)");
        st.executeUpdate("insert into t5 values(0)");
        st.executeUpdate("insert into t5 values(1)");
        st.executeUpdate("insert into t5 values(2)");
        st.executeUpdate("insert into t5 values(3)");
        st.executeUpdate("insert into t5 values(4)");
        st.executeUpdate("create table t6(x6 int)");
        st.executeUpdate("insert into t6 values(0)");
        st.executeUpdate("insert into t6 values(1)");
        st.executeUpdate("insert into t6 values(2)");
        st.executeUpdate("insert into t6 values(3)");
        st.executeUpdate("insert into t6 values(4)");
        st.executeUpdate("insert into t6 values(5)");
        st.executeUpdate("create table t7(x7 int)");
        st.executeUpdate("insert into t7 values(0)");
        st.executeUpdate("insert into t7 values(1)");
        st.executeUpdate("insert into t7 values(2)");
        st.executeUpdate("insert into t7 values(3)");
        st.executeUpdate("insert into t7 values(4)");
        st.executeUpdate("insert into t7 values(5)");
        st.executeUpdate("insert into t7 values(6)");
        st.executeUpdate("insert into t0 values(1)");
        st.executeUpdate("insert into t1 values(2)");
        st.executeUpdate("insert into t0 values(3)");
        st.executeUpdate("insert into t1 values(3)");
        st.executeUpdate("insert into t2 values(4)");
        st.executeUpdate("insert into t0 values(5)");
        st.executeUpdate("insert into t2 values(5)");
        st.executeUpdate("insert into t1 values(6)");
        st.executeUpdate("insert into t2 values(6)");
        st.executeUpdate("insert into t0 values(7)");
        st.executeUpdate("insert into t1 values(7)");
        st.executeUpdate("insert into t2 values(7)");
        st.executeUpdate("insert into t3 values(8)");
        st.executeUpdate("insert into t0 values(9)");
        st.executeUpdate("insert into t3 values(9)");
        st.executeUpdate("insert into t1 values(10)");
        st.executeUpdate("insert into t3 values(10)");
        st.executeUpdate("insert into t0 values(11)");
        st.executeUpdate("insert into t1 values(11)");
        st.executeUpdate("insert into t3 values(11)");
        st.executeUpdate("insert into t2 values(12)");
        st.executeUpdate("insert into t3 values(12)");
        st.executeUpdate("insert into t0 values(13)");
        st.executeUpdate("insert into t2 values(13)");
        st.executeUpdate("insert into t3 values(13)");
        st.executeUpdate("insert into t1 values(14)");
        st.executeUpdate("insert into t2 values(14)");
        st.executeUpdate("insert into t3 values(14)");
        st.executeUpdate("insert into t0 values(15)");
        st.executeUpdate("insert into t1 values(15)");
        st.executeUpdate("insert into t2 values(15)");
        st.executeUpdate("insert into t3 values(15)");

        rs = st.executeQuery(
        "SELECT t0.x0, " +
        "       t1.x1," +
        "       t2.x2," +
        "       t3.x3," +
        "       t4.x4," +
        "       t5.x5," +
        "       t6.x6," +
        "       t7.x7 " +
        "FROM         " +
        " ((t0                                                               " +
        "   LEFT OUTER JOIN ((t1                                             " +
        "                     LEFT OUTER JOIN (t2                            " +
        "                                      LEFT OUTER JOIN t3            " +
        "                                        ON t2.x2 = t3.x3 )          " +
        "                       ON t1.x1 = t2.x2 )                           " +
        "                    LEFT OUTER JOIN (t4                             " +
        "                                     INNER JOIN (t5                 " +
        "                                                 LEFT OUTER JOIN t6 " +
        "                                                   ON t5.x5 = t6.x6)" +
        "                                       ON t4.x4 = t5.x5 )           " +
        "                      ON t1.x1 = t5.x5 )                            " +
        "     ON t0.x0 = t5.x5 )                                             " +
        "  LEFT OUTER JOIN t7                                                " +
        "    ON t3.x3 = t7.x7 )                                              ");

        expRS = new String [][]
        {
            {"1", "1", null, null, null, null, null, null},
            {"3", "3", "3", null, "3", "3", "3", null},
            {"5", "5", null, null, null, null, null, null},
            {"7", "7", null, null, null, null, null, null},
            {"9", "9", null, null, null, null, null, null},
            {"11", "11", null, null, null, null, null, null},
            {"13", "13", null, null, null, null, null, null},
            {"15", "15", null, null, null, null, null, null}
        };

        JDBC.assertFullResultSet(rs, expRS);
    }
}
