/*
 * Class org.apache.derbyTesting.functionTests.tests.lang.OrderByAndOffsetFetchInSubqueries
 *
 * 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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.RuntimeStatisticsParser;
import org.apache.derbyTesting.junit.SQLUtilities;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * Tests for DERBY-4397 Allow {@code ORDER BY} in subqueries
 * and       DERBY-4398 Allow {@code OFFSET/FETCH} in subqueries.
 */
public class OrderByAndOffsetFetchInSubqueries extends BaseJDBCTestCase {

    final static String SYNTAX_ERROR = "42X01";
    final static String COLUMN_NOT_FOUND = "42X04";
    final static String COLUMN_OUT_OF_RANGE = "42X77";
    final static String ORDER_BY_COLUMN_NOT_FOUND = "42X78";

    public OrderByAndOffsetFetchInSubqueries(String name) {
        super(name);
    }

    /**
     * Construct top level suite in this JUnit test
     *
     * @return A suite containing embedded and client suites.
     */
    public static Test suite()
    {
        BaseTestSuite suite =
            new BaseTestSuite("OrderByAndOffsetFetchInSubqueries");

        suite.addTest(makeSuite());
        suite.addTest(
             TestConfiguration.clientServerDecorator(makeSuite()));

        return suite;
    }

    /**
     * Construct suite of tests
     *
     * @return A suite containing the test cases.
     */
    private static Test makeSuite()
    {
        return new CleanDatabaseTestSetup(
            new BaseTestSuite(OrderByAndOffsetFetchInSubqueries.class)) {
                @Override
                protected void decorateSQL(Statement s)
                        throws SQLException {
                    getConnection().setAutoCommit(false);

                    s.execute("create table temp1(s varchar(10))");

                    // GENERATED ALWAYS AS IDENTITY
                    s.execute("create table temp2(" +
                              "i integer not null " +
                              "    generated always as identity," +
                              "s varchar(10))");
                    s.execute("create table temp2b(" +
                              "i integer not null " +
                              "    generated always as identity," +
                              "s varchar(10))");
                    // DEFAULT value
                    s.execute("create table temp3(" +
                              "i integer not null " +
                              "    generated always as identity," +
                              "s varchar(10)," +
                              "j integer not null " +
                              "    default 66," +
                              "t varchar(10))");

                    // GENERATED ALWAYS AS (expression)
                    s.execute("create table temp4(" +
                              "i integer not null " +
                              "    generated always as identity," +
                              "s varchar(10)," +
                              "j integer not null " +
                              "    generated always as (2*i)," +
                              "t varchar(10))");


                    s.execute("create table t01(c1 int)");
                    s.execute("create table t02(c2 int)");

                    s.execute("create table t_source(c1 int, c2 varchar(10))");
                    s.execute("create table t(i int not null, " +
                              "               constraint c unique (i), " +
                              "               j int, k int)");

                    getConnection().commit();
                }
            };
    }

    /**
     * Test {@code INSERT INTO t SELECT .. FROM .. ORDER BY}.
     *
     * @throws java.sql.SQLException
     */
    public void testInsertSelectOrderBy() throws SQLException {
        //
        // Shows that DERBY-4 is now solved.
        //
        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.execute("insert into temp1 values 'x','a','c','b','a'");
        s.execute("insert into temp2(s) select s from temp1 order by s");
        s.execute("insert into temp2(s) select s as a1 from temp1 order by a1");
        s.execute("insert into temp2(s) select * from temp1 order by s");

        rs = s.executeQuery("select * from temp2");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "a"},
                {"2", "a"},
                {"3", "b"},
                {"4", "c"},
                {"5", "x"},
                {"6", "a"},
                {"7", "a"},
                {"8", "b"},
                {"9", "c"},
                {"10", "x"},
                {"11", "a"},
                {"12", "a"},
                {"13", "b"},
                {"14", "c"},
                {"15", "x"}});

        rs = s.executeQuery("select * from temp2 order by i");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "a"},
                {"2", "a"},
                {"3", "b"},
                {"4", "c"},
                {"5", "x"},
                {"6", "a"},
                {"7", "a"},
                {"8", "b"},
                {"9", "c"},
                {"10", "x"},
                {"11", "a"},
                {"12", "a"},
                {"13", "b"},
                {"14", "c"},
                {"15", "x"}});

        s.execute("insert into temp2(s) select s as a1 from temp1 order by s");

        // This should be rejected as "no such column" errors:
        assertStatementError(
            COLUMN_NOT_FOUND, s,
            "insert into temp2(s) select s as a1 from temp1 order by no_such");

        // A similar example, but with integers rather than strings, and some
        // intermediate select statements to show that the ordering is working.
        //
        s.execute("insert into t01 values (50), (10), (1000), (15), (51)");

        rs = s.executeQuery("select * from t01");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"50"},
                {"10"},
                {"1000"},
                {"15"},
                {"51"}});


        s.execute("insert into t02 select * from t01 order by c1");
        s.execute("insert into t02 select * from t01");
        s.execute("insert into t02 select * from t01 order by c1");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"10"},
                {"15"},
                {"50"},
                {"51"},
                {"1000"},
                {"50"},
                {"10"},
                {"1000"},
                {"15"},
                {"51"},
                {"10"},
                {"15"},
                {"50"},
                {"51"},
                {"1000"}});

        // Combining ORDER BY and VALUES is not legal SQL, cf.  SQL 2008,
        // section 14.11, Syntactic Rule 17: "A <query expression> simply
        // contained in a <from subquery> shall not be a <table value
        // constructor>. See also discussion in JIRA on DERBY-4413
        // (2009-OCT-23).
        //
        assertStatementError(
             SYNTAX_ERROR, s,
             "insert into t02 values 66 order by 1");
        assertStatementError(
             SYNTAX_ERROR, s,
             "insert into t02 values (901), (920), (903) order by 1");

        // But this should work:
        s.executeUpdate("delete from t02");
        s.executeUpdate("insert into t02 select 900 from sys.systables " +
                        "                union values 66 order by 1");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                 {"66"},
                 {"900"}});

        // other way around:
        s.executeUpdate("delete from t02");
        s.executeUpdate(
            "insert into t02 values 66 " +
            "       union select 900 from sys.systables order by 1");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                 {"66"},
                 {"900"}});

        // and, somewhat perversely (since a plain "values 66 order by 1" is
        // illegal), this:
        s.executeUpdate("delete from t02");
        s.executeUpdate("insert into t02 values 66 " +
                        "       union values 66 order by 1");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                 {"66"}});


        // UNION
        //
        // ok:
        s.execute("delete from t02");
        s.execute("insert into t02 select * from t01 union all " +
                  "                select * from t01 order by c1");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"10"},
                {"10"},
                {"15"},
                {"15"},
                {"50"},
                {"50"},
                {"51"},
                {"51"},
                {"1000"},
                {"1000"}});

        // Not ok (c1 is not a column in the union result set, since t02 has
        // column c02.
        assertStatementError(
            ORDER_BY_COLUMN_NOT_FOUND, s,
            "insert into t02 select * from t01 union all " +
            "                select * from t02 order by c1");


        // Complication: project away sort column
        s.execute("delete from t02");
        s.execute("insert into t_source " +
                  "    values (1, 'one'), (2, 'two'), (8, 'three')");
        s.execute("insert into t_source(c1) " +
                  "    select c1 from t_source order by c2 desc");
        rs = s.executeQuery("select * from t_source");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "one"},
                {"2", "two"},
                {"8", "three"},
                {"2", null},
                {"8", null},
                {"1", null}});

        // DERBY-4496
        s.executeUpdate("create table t4496(x varchar(100))");
        s.execute("insert into t4496(x) select ibmreqd from " +
                  "    (select * from sysibm.sysdummy1" +
                  "         order by length(ibmreqd)) t1");

        JDBC.assertFullResultSet(
            s.executeQuery("select * from t4496"),
            new String[][]{{"Y"}});

        // DERBY-6006. INSERT INTO ... SELECT FROM could fail with a
        // NullPointerException in insane builds, or XSCH5 or assert in sane
        // builds, if the SELECT had an ORDER BY column that was not referenced
        // in the select list, and if normalization was required because the
        // types in the select list didn't exactly match the types in the
        // target table.
        //
        // In the test case below, the select list has an INT (the literal 1),
        // whereas the target type is DOUBLE. Also, the ORDER BY column (X) is
        // not in the select list.
        s.execute("create table t6006(x double)");
        assertUpdateCount(s, 6, "insert into t6006 values 1,2,3,4,5,6");
        assertUpdateCount(s, 6,
                "insert into t6006 select 1 from t6006 order by x");

        rollback();
    }

    /**
     * Same test as {@code testInsertSelectOrderBy} but with use of
     * {@code OFFSET/FETCH FIRST}.
     * <p/>
     * Test {@code INSERT INTO t SELECT .. FROM .. ORDER BY} + {@code OFFSET
     * FETCH}
     * <p/>
     * This test is a variant made my modifying {@code testInsertSelectOrderBy}
     * with suitable {@code OFFSET/FETCH FIRST} clauses.
     *
     * @throws java.sql.SQLException
     */
    public void testInsertSelectOrderByOffsetFetch() throws SQLException {
        //
        // Shows that DERBY-4 is now solved.
        //
        setAutoCommit(false);
        Statement s = createStatement();

        s.execute("insert into temp1 values 'x','a','c','b','a'");
        s.execute("insert into temp2b(s) select s from temp1 order by s " +
                  "    offset 1 rows fetch next 4 rows only");

        JDBC.assertFullResultSet(
            s.executeQuery("select * from temp2b"),
            new String[][]{
                {"1", "a"},
                {"2", "b"},
                {"3", "c"},
                {"4", "x"}});

        s.execute(
            "insert into temp2b(s) select s as a1 from temp1 order by a1" +
            "    offset 1 rows fetch next 4 rows only");

        s.execute(
            "insert into temp2b(s) select * from temp1 order by s " +
            "    offset 1 rows fetch next 4 rows only");

        ResultSet rs = s.executeQuery("select * from temp2b");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "a"},
                {"2", "b"},
                {"3", "c"},
                {"4", "x"},
                {"5", "a"},
                {"6", "b"},
                {"7", "c"},
                {"8", "x"},
                {"9", "a"},
                {"10", "b"},
                {"11", "c"},
                {"12", "x"}});

        rs = s.executeQuery("select * from temp2b order by i");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "a"},
                {"2", "b"},
                {"3", "c"},
                {"4", "x"},
                {"5", "a"},
                {"6", "b"},
                {"7", "c"},
                {"8", "x"},
                {"9", "a"},
                {"10", "b"},
                {"11", "c"},
                {"12", "x"}});


        // A similar example, but with integers rather than strings, and some
        // intermediate select statements to show that the ordering is working.
        //
        s.execute("insert into t01 values (50), (10), (1000), (15), (51)");

        rs = s.executeQuery("select * from t01");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"50"},
                {"10"},
                {"1000"},
                {"15"},
                {"51"}});


        s.execute(
            "insert into t02 select * from t01 order by c1 " +
            "    fetch first 2 rows only");
        s.execute(
            "insert into t02 select * from t01");
        s.execute(
            "insert into t02 select * from t01 order by c1 offset 0 rows");

        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"10"},
                {"15"},
                {"50"},
                {"10"},
                {"1000"},
                {"15"},
                {"51"},
                {"10"},
                {"15"},
                {"50"},
                {"51"},
                {"1000"}});

        // Illegal context
        assertStatementError(
             SYNTAX_ERROR, s,
             "insert into t02 values 66 offset 1 row");

        // But this should work:
        s.executeUpdate("delete from t02");
        s.executeUpdate(
            "insert into t02 select 900 from sys.systables " +
            "                union values 66 order by 1 offset 1 row");

        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                 {"900"}});

        // other way around:
        s.executeUpdate("delete from t02");
        s.executeUpdate(
            "insert into t02 values 66 " +
            "       union select 900 from sys.systables fetch next 1 row only");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                 {"66"}});

        s.executeUpdate("delete from t02");
        s.executeUpdate("insert into t02 select * from (values 3,4,5 )v " +
                        "    order by 1 offset 1 row fetch next 2 rows only");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"4"},
                {"5"}});


        // UNION
        //
        // ok:
        s.execute("delete from t02");
        s.execute("insert into t02 select * from t01 union all " +
                  "                select * from t01 order by c1 " +
                  "                fetch next 4 rows only");
        rs = s.executeQuery("select * from t02");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"10"},
                {"10"},
                {"15"},
                {"15"}});

        // EXCEPT
        s.execute("delete from t01");
        s.execute("insert into t02 values 6,7");
        s.execute("insert into t01 select * from t02 except " +
                  "                values 10 order by 1 offset 1 row");
        rs = s.executeQuery("select * from t01");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"7"},
                {"15"}});

        // Complication: project away sort column
        s.execute("delete from t02");
        s.execute("insert into t_source " +
                  "    values (1, 'one'), (2, 'two'), (8, 'three')");
        s.execute("insert into t_source(c1) " +
                  "    select c1 from t_source order by c2 desc " +
                  "    fetch next 2 rows only");
        rs = s.executeQuery("select * from t_source");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "one"},
                {"2", "two"},
                {"8", "three"},
                {"2", null},
                {"8", null}});

        rollback();
    }


    /**
     * {@code SELECT} subqueries with {@code ORDER BY}
     *
     * @throws java.sql.SQLException
     */
    public void testSelectSubqueriesOrderBy() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.execute(
            "insert into t_source values (1, 'one'), (2, 'two'), (8, 'three')");

        /*
         * Simple SELECT FromSubquery
         */
        rs = s.executeQuery(
            "select * from (select c1 from t_source order by c1 desc) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8"}, {"2"}, {"1"}});

        rs = s.executeQuery(
            "select * from (select c1+1 from t_source order by c1+1 desc) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"9"}, {"3"}, {"2"}});

        rs = s.executeQuery(
            "select * from (select c1,c2 from t_source order by c1 desc,2) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8", "three"}, {"2", "two"}, {"1", "one"}});

        // Complication: project away sort column
        rs = s.executeQuery(
            "select * from (select c2 from t_source order by c1 desc) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"three"}, {"two"}, {"one"}});

        rs = s.executeQuery(
            "select * from " +
            "    (select c2 from t_source order by c1 desc) s order by 1");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"one"}, {"three"}, {"two"}});

        /*
         * Simple VALUES FromSubquery
         */
        rs = s.executeQuery(
            "select * from (values (1, 'one'), (2, 'two'), (8, 'three')" +
            "               order by 1 desc) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8", "three"}, {"2", "two"}, {"1", "one"}});


        /*
         * ORDER BY in EXISTS subquery
         */
        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c1)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"}, {"2"}, {"8"}});

        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c1 desc)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"}, {"2"}, {"8"}});

        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c2 desc)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"}, {"2"}, {"8"}});

        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c2 desc) order by 1 desc");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8"}, {"2"}, {"1"}});

        /*
         * NOT EXISTS
         */
        rs = s.executeQuery(
            "select c1 from t_source where not exists " +
            "    (select c1 from t_source order by c2 desc) order by 1 desc");
        JDBC.assertEmpty(rs);

        rs = s.executeQuery(
            "select c1 from t_source ot where not exists " +
            "   (select c1 from t_source where ot.c1=(c1/2) order by c2 desc)" +
            "    order by 1 desc");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8"}, {"2"}});

        /*
         * IN subquery
         */
        s.executeUpdate("insert into t values (1,10,1), (2,40,1)," +
                        "         (3,45,1), (4,46,1), (5,90,1)");
        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by 1 desc)");
        JDBC.assertFullResultSet(rs, new String[][]{{"1", "10", "1"}});

        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by i/5 desc)");
        JDBC.assertFullResultSet(rs, new String[][]{{"1", "10", "1"}});

        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by j)");
        JDBC.assertFullResultSet(rs, new String[][]{{"1", "10", "1"}});


        /*
         * Scalar subquery inside ALL subquery with correlation
         */
        String[][] expected = new String[][]{
            {"1", "10", "1"},
            {"2", "40", "1"}};

        // First without any ORDER BYs
        rs = s.executeQuery(
            "select * from t t_o where i <= all (" +
            "    select i+1 from t where i = t_o.k + (" +
            "        select count(*) from t) - 5)");
        JDBC.assertFullResultSet(rs, expected);

        // Then with ORDER BY at both subquery levels; should be the same result
        rs = s.executeQuery(
            "select * from t t_o where i <= all (" +
            "    select i+1 from t where i = t_o.k + (" +
            "        select count(*) from t order by 1) - 5 " +
            "    order by 1 desc)");
        JDBC.assertFullResultSet(rs, expected);

        rollback();
    }


    /**
     * {@code SELECT} subqueries with {@code ORDER BY} and {@code OFFSET/FETCH}.
     * <p/>
     * This test is a variant made my modifying {@code
     * testSelectSubqueriesOrderBy} with suitable {@code OFFSET/FETCH FIRST}
     * clauses.
     *
     * @throws java.sql.SQLException
     */
    public void testSelectSubqueriesOrderByAndOffsetFetch()
            throws SQLException {

        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.execute(
            "insert into t_source values (1, 'one'), (2, 'two'), (8, 'three')");

        /*
         * Simple SELECT FromSubquery
         */
        rs = s.executeQuery(
            "select * from (select c1 from t_source order by c1 desc " +
            "               offset 1 row) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"2"}, {"1"}});

        rs = s.executeQuery(
            "select * from (select c1+1 from t_source order by c1+1 desc " +
            "               fetch first 2 rows only) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"9"}, {"3"}});

        rs = s.executeQuery(
            "select * from (select c1,c2 from t_source order by c1 desc,2 " +
            "               offset 2 rows fetch next 1 row only) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1", "one"}});

        // Complication: project away sort column
        rs = s.executeQuery(
            "select * from (select c2 from t_source order by c1 desc " +
            "               offset 2 rows) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"one"}});

        rs = s.executeQuery(
            "select * from " +
            "    (select c2 from t_source order by c1 desc " +
            "     fetch first 2 row only) s order by 1");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"three"}, {"two"}});

        /*
         * Simple VALUES FromSubquery
         */
        rs = s.executeQuery(
            "select * from (values (1, 'one'), (2, 'two'), (8, 'three')" +
            "               order by 1 desc offset 1 row) s");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"2", "two"}, {"1", "one"}});


        /*
         * ORDER BY in EXISTS subquery
         */
        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c1 offset 1 row)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"}, {"2"}, {"8"}});

        // OFFSET so we get an empty result set:
        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c1 offset 3 rows)");
        JDBC.assertEmpty(rs);

        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c1 desc " +
            "     fetch first 1 row only)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"}, {"2"}, {"8"}});

        // drop order by for once:
        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source offset 1 row)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"}, {"2"}, {"8"}});

        rs = s.executeQuery(
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c2 desc " +
            "     offset 1 row fetch first 1 row only) " +
            "  order by 1 desc offset 1 row fetch first 1 row only");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"2"}});

        /*
         * NOT EXISTS
         */

        // We offset outside inner subquery, so NOT EXISTS should hold for all
        rs = s.executeQuery(
            "select c1 from t_source where not exists " +
            "    (select c1 from t_source order by c2 desc " +
            "         offset 3 rows) " +
            "    order by 1 desc");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8"}, {"2"}, {"1"}});

        // should remove the hit for 1 below since we offset past it:
        rs = s.executeQuery(
            "select c1 from t_source ot where not exists " +
            "   (select c1 from t_source where ot.c1=(c1/2) order by c2 desc " +
            "        offset 1 row)" +
            "   order by 1 desc");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"8"}, {"2"}, {"1"}});

        /*
         * IN subquery
         */
        s.executeUpdate("insert into t values (1,10,1), (2,40,1)," +
                        "         (3,45,1), (4,46,1), (5,90,1)");

        // offset away the interesting value in the subquery:
        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by 1 desc " +
            "                            offset 1 row)");
        JDBC.assertEmpty(rs);

        // turn rs around, and we should get a hit:
        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by 1 asc " +
            "                            offset 1 row)");
        JDBC.assertFullResultSet(rs, new String[][]{{"1", "10", "1"}});

        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by i/5 desc " +
            "                            offset 1 row)");
        JDBC.assertEmpty(rs);

        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by i/5 asc " +
            "                            offset 1 row)");
        JDBC.assertFullResultSet(rs, new String[][]{{"1", "10", "1"}});

        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by j " +
            "                            offset 1 row)");
        JDBC.assertFullResultSet(rs, new String[][]{{"1", "10", "1"}});

        rs = s.executeQuery(
            "select * from t where i in (select i/5 from t order by j desc " +
            "                            offset 1 row)");
        JDBC.assertEmpty(rs);

        /*
         * Scalar subquery inside ALL subquery with correlation
         */
        String[][] expected = new String[][]{
            {"1", "10", "1"},
            {"2", "40", "1"}};

        // First without any ORDER BYs
        rs = s.executeQuery(
            "select * from t t_o where i <= all (" +
            "    select i+1 from t where i = t_o.k + (" +
            "        select count(*) from t) - 5)");
        JDBC.assertFullResultSet(rs, expected);

        // Should give null from subquery
        rs = s.executeQuery(
            "select * from t where i = (select count(*) from t order by 1 " +
            "                           offset 1 row)");
        JDBC.assertEmpty(rs);

        rs = s.executeQuery(
            "select * from t t_o where i <= all (" +
            "    select i+1 from t where i = t_o.k + cast(null as int) +" +
            "         - 5 " +
            "    order by 1 desc)");
        // Notice the cast(null as int) I use above to check that the
        // subquery in the next query using an offset which makes the scalar
        // subquery return null gives the same result as this one.
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1","10","1"},
                {"2","40","1"},
                {"3","45","1"},
                {"4","46","1"},
                {"5","90","1"}});

        rs = s.executeQuery(
            "select * from t t_o where i <= all (" +
            "    select i+1 from t where i = t_o.k + (" +
            "        select count(*) from t order by 1 offset 1 row) - 5 " +
            "    order by 1 desc)");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1","10","1"},
                {"2","40","1"},
                {"3","45","1"},
                {"4","46","1"},
                {"5","90","1"}});

        rollback();
    }


    /**
     * Test JOIN with delimited subqueries
     *
     * @throws java.sql.SQLException
     */
    public void testJoinsWithOffsetFetch() throws SQLException {

        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.execute("insert into temp1 values 'x','a','c','b','a'");

        PreparedStatement ps = prepareStatement(
            "select * from " +
            "   (select s from temp1 order by s " +
            "                        fetch first ? rows only) t1 join " +
            "   (select s from temp1 order by s offset ? row " +
            "                        fetch first ? row only) t2 " +
            "   on t1.s=t2.s");

        ps.setInt(1,2);
        ps.setInt(2,1);
        ps.setInt(3,1);

        rs = ps.executeQuery();

        JDBC.assertFullResultSet(rs, new String[][]{
                {"a", "a"},
                {"a", "a"}});

        ps.setInt(1,1);
        rs = ps.executeQuery();

        JDBC.assertFullResultSet(rs, new String[][]{
                {"a", "a"}});

        rollback();
    }


    /**
     * Test {@code ORDER BY} in a view definition
     *
     * @throws java.sql.SQLException
     */
    public void testView() throws SQLException {

        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.executeUpdate(
            "create view v1 as select i from t order by j desc");
        s.executeUpdate(
            "create view v2 as select i from t order by i");
        s.executeUpdate(
            "insert into t values (1,10,1), (2,40,1)," +
            "         (3,45,1), (4,46,1), (5,90,1)");
        rs = s.executeQuery(
            "select i from v1");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"5"},{"4"},{"3"},{"2"},{"1"}});

        rs = s.executeQuery(
            "select i from v2");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"},{"2"},{"3"},{"4"},{"5"}});

        rollback();
    }

    /**
     * Test {@code ORDER BY} + {@code FETCH/OFFSET} in a view definition
     * <p/>
     * This test is a variant made my modifying {@code testView} with suitable
     * {@code OFFSET/FETCH FIRST} clauses.
     *
     * @throws java.sql.SQLException
     */
    public void testViewFetchOffset() throws SQLException {

        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.executeUpdate(
            "create view v1 as select i from t order by j desc " +
            "                  offset 2 rows fetch first 1 row only");
        s.executeUpdate(
            "create view v2 as select i from t order by i " +
            "                  fetch next 2 rows only");

        s.executeUpdate(
            "insert into t values (1,10,1), (2,40,1)," +
            "         (3,45,1), (4,46,1), (5,90,1)");

        rs = s.executeQuery(
            "select i from v1");
        JDBC.assertFullResultSet(rs, new String[][]{{"3"}});

        rs = s.executeQuery(
            "select i from v2");
        JDBC.assertFullResultSet(rs, new String[][]{
                {"1"},{"2"}});

        rollback();
    }


    /**
     * {@code SELECT} subqueries with {@code ORDER BY} - negative tests
     *
     * @throws java.sql.SQLException
     */
    public void testSelectSubqueriesOrderByNegative() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;

        s.execute(
            "insert into t_source values (1, 'one'), (2, 'two'), (8, 'three')");

        /*
         * Simple SELECT FromSubquery
         */
        assertStatementError(
            COLUMN_NOT_FOUND, s,
            "select * from (select c1 from t_source order by c3 desc) s");

        assertStatementError(
            COLUMN_OUT_OF_RANGE, s,
            "select * from (select c1 from t_source order by 3 desc) s");


        /*
         * Simple VALUES FromSubquery
         */
        assertStatementError(
            COLUMN_OUT_OF_RANGE, s,
            "select * from (values (1, 'one'), (2, 'two'), (8, 'three')" +
            "               order by 3 desc) s");

        /*
         * ORDER BY in EXISTS subquery:
         */
        assertStatementError(
            COLUMN_NOT_FOUND, s,
            "select c1 from t_source where exists " +
            "    (select c1 from t_source order by c4)");

        rollback();
    }


    /**
     * {@code SELECT} subqueries with {@code ORDER BY} - check sort avoidance
     *
     * @throws java.sql.SQLException
     */
    public void testSelectSubqueriesSortAvoidance() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();
        ResultSet rs;
        RuntimeStatisticsParser rtsp;
        s.executeUpdate("create table ts(i int, j int)");
        PreparedStatement ps = prepareStatement("insert into ts values(?,?)");
        for (int i=0; i < 100; i++) {
            ps.setInt(1,i);
            ps.setInt(2,i*2);
            ps.execute();
        }

        s.executeUpdate("create unique index t_i on ts(i)");
        s.execute("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");

        // ORDER BY inside a subquery should make use of index to avoid
        // sorting.
        rs = s.executeQuery("select * from (select i from ts order by i)tt");
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);

        // Verify that we use the index scan here and no sorting is incurred
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TS","T_I"));
        assertFalse(rtsp.whatSortingRequired());

        s.execute("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(0)");
        rollback();
    }

    /**
     * Prevent pushing of where predicates into selects with fetch
     * and/or offset (DERBY-5911). Similarly, for windowed selects.
     *
     * @throws java.sql.SQLException
     */
    public void testPushAvoidance() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();
        s.executeUpdate
            ("CREATE TABLE COFFEES (COF_NAME VARCHAR(254),PRICE INTEGER)");
        s.executeUpdate
            ("INSERT INTO COFFEES VALUES ('Colombian', 5)");
        s.executeUpdate
            ("INSERT INTO COFFEES VALUES ('French_Roast', 5)");
        s.executeUpdate
            ("INSERT INTO COFFEES VALUES ('Colombian_Decaf', 20)");

        ResultSet rs = s.executeQuery
            ("select * from " +
             "    (select COF_NAME, PRICE from COFFEES " +
             "     order by COF_NAME fetch next 2 rows only" +
             "    ) t " +
             "where t.PRICE < 10");

        JDBC.assertFullResultSet(rs, new String[][]{{"Colombian", "5"}});

        rs = s.executeQuery
            ("select * from " +
             "    (select COF_NAME, PRICE from COFFEES " +
             "     order by COF_NAME offset 2 row" +
             "    ) t " +
             "where t.PRICE < 10");

        JDBC.assertFullResultSet(rs, new String[][]{{"French_Roast", "5"}});

        rs = s.executeQuery
            ("select cof_name, price from " +
             "   (select row_number() over() as rownum, COF_NAME, PRICE from " +
             "      (select * from COFFEES order by COF_NAME) i" +
             "   ) t where rownum <= 2 and PRICE < 10");

        JDBC.assertFullResultSet(rs, new String[][]{{"Colombian", "5"}});


        rollback();
    }


    /**
     * Test nesting inside set operands, cf. this production in SQL
     * 2011, section 7.12:
     * <pre>
     * <query primary> ::=
     *      <simple table>
     *   |  <left paren> <query expression body>
     *      [ <order by clause> ] [ <result offset clause> ]
     *      [ <fetch first clause> ] <right paren>
     * </pre>
     * The corresponding production in {@code sqlgrammar.jj} is
     * {@code nonJoinQueryPrimary}.
     *
     * Cf. DERBY-6008.
     *
     * @throws java.sql.SQLException
     */
    public void testNestingInsideSetOperation() throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();

        s.executeUpdate("create table t1(i int, j int )");
        s.executeUpdate("create table t2(i int, j int)");

        s.executeUpdate("insert into t1 values (1,1),(4,8),(2,4)");
        s.executeUpdate("insert into t2 values (10,10),(40,80),(20,40)");

        ResultSet rs = s.executeQuery(
            "(select i from t1 order by j desc offset 1 row) union " +
            "(select i from t2 order by j desc offset 1 rows " +
                                             "fetch next 1 row only)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"1"}, {"2"}, {"20"}});

        // Without parentheses, expect syntax error
        assertCompileError("42X01",
                "select i from t1 order by j desc offset 1 row union " +
                "(select i from t2 order by j desc offset 2 rows)");

        // With VALUES (single) instead of SELECT:
        // Single values exercise changes in RowResultSetNode
        rs = s.executeQuery(
            "(values 1 order by 1 fetch first 1 row only) union " +
            "(select i from t2 order by j desc offset 2 rows)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"1"}, {"10"}});

        // With VALUES (single) instead of SELECT and duplicate ordering key
        rs = s.executeQuery(
            "(values 1 order by 1,1 fetch first 1 row only) union " +
            "(select i from t2 order by j desc offset 2 rows)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"1"}, {"10"}});

        // With VALUES (multiple) instead of SELECT
        // Multiples values exercise changes in SetOperatorNode when used in
        // table value constructor context (UNION).
        rs = s.executeQuery(
            "(values 1,2 order by 1 desc offset 1 row " +
            "                       fetch first 1 row only)" +
            " union (select i from t2 order by j desc offset 2 rows)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"1"}, {"10"}});

        // With VALUES (multiple) instead of SELECT plus duplicate ordering
        // key
        rs = s.executeQuery(
            "(values 1,2 order by 1,1 offset 1 row fetch first 1 row only)" +
            " union (select i from t2 order by j desc offset 2 rows)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"2"}, {"10"}});

        // Intersect and except
        s.executeUpdate(
                "create table countries(name varchar(20), " +
                "                       population int, " +
                "                       area int)");

        s.executeUpdate("insert into countries values" +
                "('Norway', 5033675, 385252)," +
                "('Sweden', 9540065, 449964)," +
                "('Denmark', 5580413, 42894)," +
                "('Iceland', 320060, 103001)," +
                "('Liechtenstein', 36281, 160)");

        rs = s.executeQuery(
           "(select name from countries " +
           "    order by population desc fetch first 2 rows only)" +
           " intersect " +
           "(select name from countries " +
           "    order by area desc fetch first 2 rows only)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"Sweden"}});

        rs = s.executeQuery(
           "(values ('Norway', 5033675, 385252), " +
           "        ('Sweden', 9540065, 449964)," +
           "        ('Denmark', 5580413, 42894)," +
           "        ('Iceland', 320060, 103001)," +
           "        ('Liechtenstein', 36281, 160)" +
           "    order by 2 desc fetch first 3 rows only)" +
           " intersect " +
           "(select * from countries " +
           "    order by area desc fetch first 3 rows only)");
        // Note: we use 3 rows here to check that both sorts work the way they
        // should: at the lowest level, the "order by 2 desc", then the
        // "fetch first" of only three of those rows, then on top the ascending
        // sort on all columns to get the data ready for the intersect.
        JDBC.assertFullResultSet(rs, new String[][]{
            {"Norway", "5033675", "385252"},
            {"Sweden", "9540065", "449964"}});

        rs = s.executeQuery(
           "(values ('Norway', 5033675, 385252)" +
           "    order by 2 desc fetch first 3 rows only)" +
           " intersect " +
           "(values ('Norway', 5033675, 385252))");
        JDBC.assertFullResultSet(rs, new String[][]{
            {"Norway", "5033675", "385252"}});

        rs = s.executeQuery(
           "(select name from countries " +
           "    order by population desc fetch first 2 rows only)" +
           " except " +
           "(select name from countries " +
           "    order by area desc fetch first 2 rows only)");

        JDBC.assertFullResultSet(rs, new String[][]{
            {"Denmark"}});

        rs = s.executeQuery(
           "(values ('Norway', 5033675, 385252), " +
           "        ('Sweden', 9540065, 449964)," +
           "        ('Denmark', 5580413, 42894)," +
           "        ('Iceland', 320060, 103001)," +
           "        ('Liechtenstein', 36281, 160)" +
           "    order by 2 desc fetch first 3 rows only)" +
           " except " +
           "(select * from countries " +
           "    order by area desc fetch first 3 rows only)");
        JDBC.assertFullResultSet(rs, new String[][]{
            {"Denmark", "5580413", "42894"}});

        rollback();
    }

    /**
     * Nested query expression body, with each level contributing to the set of
     * ORDER BY and/or OFFSET/FETCH FIRST clauses.
     *
     * Cf. these productions in SQL 2011, section 7.11:
     *
     * <pre>
     * <query expression> ::=
     *    [ <with clause> ] <query expression body>
     *    [ <order by clause> ] [ <result offset clause> ]
     *    [ <fetch first clause> ]
     *
     * <query expression body> ::=
     *     <query term> ...
     * </pre>
     *
     * One of the productions of {@code <query expression body>}, is
     *
     * <pre>
     *    &lt;left paren&gt; &lt;query expression body&gt;
     *    [ <order by clause> ] [ <result offset clause> ]
     *    [ <fetch first clause> ] <right paren>
     * </pre>
     * so our clauses nests to arbitrary depth given enough parentheses,
     * including ORDER BY and OFFSET/FETCH FIRST clauses. This nesting
     * did not work correctly, cf. DERBY-6378.
     *
     * The corresponding productions in {@code sqlgrammar.jj} is
     * {@code queryExpression} and {@code nonJoinQueryPrimary}.
     *
     * @throws Exception
     */
    public void testDerby6378() throws Exception
    {
        setAutoCommit(false);
        Statement stm = createStatement();
        stm.executeUpdate("create table t1 (a int, b bigint)");
        stm.executeUpdate("delete from t1");
        stm.executeUpdate("insert into t1 values " +
                "(1,-10), (2,-11), (3,-9), (4,-20), (5,-1)");

        queryAndCheck(stm,
                "(select * from t1 offset 1 row fetch first 1 row only)",
                new String [][] {{"2","-11"}});

        queryAndCheck(stm,
                "(select * from t1 order by a desc fetch first 3 rows only) " +
                "     offset 1 row fetch first 1 row only",
                new String [][] {{"4","-20"}});

        queryAndCheck(stm,
                "((select * from t1 order by a desc) " +
                "     fetch first 3 rows only)",
                new String [][] {{"5","-1"}, {"4","-20"}, {"3","-9"}});

        queryAndCheck(stm,
                "((((select * from t1 order by a desc) " +
                "        fetch first 3 rows only)) " +
                "    order by b) " +
                "fetch first 1 row only",
                new String [][] {{"4","-20"},});

        queryAndCheck(
            stm,
            "(((((values (1,-10), (2,-11), (3,-9), (4,-20), (5,-1))" +
            "            order by 1 desc) " +
            "        fetch first 3 rows only)) " +
            "    order by 2) " +
            "fetch first 1 row only",
            new String [][] {{"4","-20"},});
        rollback();
        stm.close();
    }

    private void queryAndCheck(
        Statement stm,
        String queryText,
        String [][] expectedRows) throws SQLException {

        ResultSet rs = stm.executeQuery(queryText);
        JDBC.assertFullResultSet(rs, expectedRows);
    }

}
