/**
 *  Derby - Class org.apache.derbyTesting.functionTests.tests.lang.OLAPTest
 *  
 * 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.ResultSetMetaData;
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.TestConfiguration;

/**
 * OLAP functionality test.
 * 
 * Please refer to DERBY-581 for more details.
 */ 
public class OLAPTest extends BaseJDBCTestCase {

	private final static String LANG_WINDOW_FUNCTION_CONTEXT_ERROR = "42ZC2";
	private final static String NOT_IMPLEMENTED = "0A000";
	private final static String LANG_SYNTAX_ERROR = "42X01";
	private final static String LANG_COLUMN_NOT_FOUND =	"42X04";

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

	public static Test makeSuite() {
        Test clean = new CleanDatabaseTestSetup(
            new BaseTestSuite(OLAPTest.class)) {
                protected void decorateSQL(Statement s)
                        throws SQLException
                {
                    getConnection().setAutoCommit(false);
                    s.executeUpdate("create table t1 (a int, b int)");
                    s.executeUpdate("create table t2 (x int)");
                    s.executeUpdate("create table t3 (y int)");
                    s.executeUpdate("create table t4 (a int, b int)");
                    s.executeUpdate("create table t5 (a int, b int)");

                    s.executeUpdate(
                        "insert into t1 values (10,100),(20,200)," +
                        "                      (30,300),(40,400)," +
                        "                      (50,500)");
                    s.executeUpdate(
                        "insert into t2 values (1),(2),(3),(4),(5)");
                    s.executeUpdate(
                        "insert into t3 values (4),(5),(6),(7),(8)");
                    s.executeUpdate(
                        "insert into t4 values (10,100),(20,200)");
                    s.executeUpdate(
                        "insert into t5 values (1,1),(2,4),(3,4),(4,4),(5,9)");
                    getConnection().commit();
                }
            };
		return clean;
	}


	public static Test suite()
    {
        BaseTestSuite suite = new BaseTestSuite("OLAPTest");
		suite.addTest(makeSuite());
		suite.addTest(TestConfiguration.clientServerDecorator(makeSuite()));
		return suite;
    }


	/**
	 * Main test body
	 * 
	 * @throws SQLException
	 */
	public void testBasicOperations()
		throws SQLException {
		Statement s = createStatement();

		/*
		 * Positive testing of Statements
		 */



		/*
		 * Simple queries
		 */		
		ResultSet rs = s.executeQuery("select row_number() over (), t1.* from t1");
		String[][] expectedRows = {{"1", "10", "100"}, {"2", "20", "200"}, {"3", "30", "300"}, {"4", "40", "400"}, {"5", "50", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select row_number() over (), t1.* from t1 where a > 30");
		expectedRows = new String[][]{{"1", "40", "400"}, {"2", "50", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select row_number() over (), a from t1 where b > 300");
		expectedRows = new String[][]{{"1", "40"}, {"2", "50"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select row_number() over () as r, a from t1 where b > 300");
		expectedRows = new String[][]{{"1", "40"}, {"2", "50"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Two instances of row_number columns in the same RCL */
		rs = s.executeQuery("select row_number() over (), row_number() over (), b from t1 where b <= 300");
		expectedRows = new String[][]{{"1", "1", "100"}, {"2", "2", "200"}, {"3", "3", "300"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Two instances of row_number columns in the same RCL, reorder columns */
		rs = s.executeQuery("select row_number() over (), b, row_number() over (), a from t1 where b < 300 ");
		expectedRows = new String[][]{{"1", "100", "1", "10"}, {"2", "200", "2", "20"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Pushing predicates (... where r ... ) too far cause this join to fail */
		rs = s.executeQuery("select row_number() over(),x from t2,t3 where x=y");
		expectedRows = new String[][]{{"1", "4"}, {"2", "5"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		// DERBY-4069: ORDER BY should be applied at the cursor level, that is
		// *after* a windowing clause in the. So, with the original ordering
		// here, the ROW_NUMBER should come backwards:

		/* Ordering */
		rs = s.executeQuery("select row_number() over () as r, t1.* from t1 order by b desc");

		expectedRows = new String[][]{{"5", "50", "500"},
									  {"4", "40", "400"},
									  {"3", "30", "300"},
									  {"2", "20", "200"},
									  {"1", "10", "100"}};
		JDBC.assertFullResultSet(rs, expectedRows);

        // DERBY-4069: ORDER BY should be applied at the cursor level, that is
        // *after* a windowing clause in the. So, with the original ordering
        // here, the ROW_NUMBER should come backwards:

		/* Ordering on a column dropped in projection */
		rs = s.executeQuery("select row_number() over () as r, t1.a from t1 order by b desc");

		expectedRows = new String[][]{{"5", "50"},
									  {"4", "40"},
									  {"3", "30"},
									  {"2", "20"},
									  {"1", "10"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Only expressions in RCL */
		rs = s.executeQuery("select row_number() over (), row_number() over (), 2*t1.a from t1");
		expectedRows = new String[][]{{"1", "1", "20"}, {"2", "2","40"}, {"3", "3","60"}, {"4", "4", "80"}, {"5", "5", "100"}};
		JDBC.assertFullResultSet(rs, expectedRows);



		/*
		 * Subqueries
		 */

		/* This query returned no rows at one time */
		rs = s.executeQuery("select * from (select row_number() over () as r,x from t2,t3 where x=y) s(r,x) where r < 3");
		expectedRows = new String[][]{{"1", "4"}, {"2", "5"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select * from (select row_number() over () as r, t1.* from t1) as tr where r < 3");
		expectedRows = new String[][]{{"1", "10", "100"}, {"2", "20", "200"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select * from (select row_number() over () as r, t1.* from t1) as tr where r > 3");
		expectedRows = new String[][]{{"4", "40", "400"}, {"5", "50", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Two instances of row_number columns */
		rs = s.executeQuery("select row_number() over(), tr.* from (select row_number() over () as r, t1.* from t1) as tr where r > 2 and r < 5");
		expectedRows = new String[][]{{"1", "3", "30", "300"}, {"2", "4", "40", "400"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Two instances of row_number columns, with projection */
		rs = s.executeQuery("select row_number() over(), tr.b from (select row_number() over () as r, t1.* from t1) as tr where r > 2 and r < 5");
		expectedRows = new String[][]{{"1", "300"}, {"2", "400"}};
		JDBC.assertFullResultSet(rs, expectedRows);		

		/* Column ordering */
		rs = s.executeQuery("select * from (select t1.b, row_number() over () as r from t1) as tr where r > 3");
		expectedRows = new String[][]{{"400", "4"}, {"500", "5"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		/* Column ordering with projection*/
		rs = s.executeQuery("select b from (select t1.b, row_number() over () as r from t1) as tr where r > 3");
		expectedRows = new String[][]{{"400"}, {"500"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		/*
		 * Aggregates over window functions once failed
		 */
		rs = s.executeQuery("select count(*) from (select row_number() over() from t1) x");
		expectedRows = new String[][]{{"5"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select count(*) from (select row_number() over () as r from t1) as t(r) where r <=3");
		expectedRows = new String[][]{{"3"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		/*
		 * Some other joins with window functions.
		 * Run off a smaller table t4 to reduce expected row count.
		 */
		rs = s.executeQuery("select row_number() over () from t1 union all select row_number() over () from t1");
		expectedRows = new String[][]{{"1"},{"2"},{"3"},{"4"},{"5"},{"1"},{"2"},{"3"},{"4"},{"5"}};
		JDBC.assertFullResultSet(rs, expectedRows);	
		
		rs = s.executeQuery("select 2 * r from (select row_number() over () from t1) x(r)");
		expectedRows = new String[][]{{"2"},{"4"},{"6"},{"8"},{"10"},};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select c3, c1, c2 from " + 
							"(select a, b, row_number() over() as r from t4) x1 (c1, c2, r1), " +
							"(select row_number() over() as r, b, a from t4) x2 (r2, c3, c4)");
		expectedRows = new String[][]{{"100", "10", "100"},
										{"200", "10", "100"},																				
										{"100", "20", "200"},
										{"200", "20", "200"}};										
		JDBC.assertFullResultSet(rs, expectedRows);
					
		rs = s.executeQuery("select c3, c1, c2 from " + 
							"(select a, b, row_number() over() as r from t4) x1 (c1, c2, r1), " +
							"(select row_number() over() as r, b, a from t4) x2 (r2, c3, c4), " +
							"t4");
		expectedRows = new String[][]{{"100", "10", "100"},
										{"100", "10", "100"},																				
										{"200", "10", "100"},
										{"200", "10", "100"},
										{"100", "20", "200"},
										{"100", "20", "200"},
										{"200", "20", "200"},										
										{"200", "20", "200"}};										
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select c3, c1, c2 from "+
							"(select a, b, row_number() over() as r from t4) x1 (c1, c2, r1), "+
							"(select row_number() over() as r, b, a from t4) x2 (r2, c3, c4), "+
							"t4 "+
							"where x1.r1 = 2 * x2.r2");
		expectedRows = new String[][]{{"100", "20", "200"}, {"100", "20", "200"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		rs = s.executeQuery("select c3, c1, c2 from "+
							"(select a, b, row_number() over() as r from t4) x1 (c1, c2, r1), "+
							"(select row_number() over() as r, b, a from t4) x2 (r2, c3, c4), "+
							"t4 "+
							"where x1.r1 = 2 * x2.r2");
		expectedRows = new String[][]{{"100", "20", "200"}, {"100", "20", "200"}};
		JDBC.assertFullResultSet(rs, expectedRows);
				
		/* Two problematic joins reported during development */
		rs = s.executeQuery("select c3, c1, c2 from "+
							"(select a, b, row_number() over() as r from t4) x1 (c1, c2, r1), "+
							"(select row_number() over() as r, b, a from t4) x2 (r2, c3, c4), "+
							"t4 "+
							"where x2.c4 = t4.a");
		expectedRows = new String[][]{{"100", "10", "100"}, 
										{"100", "20", "200"},
										{"200", "10", "100"},
										{"200", "20", "200"}};			
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select c3, c1, c2 from "+
							"(select a, b, row_number() over() as r from t1) x1 (c1, c2, r1), "+
							"(select row_number() over() as r, b, a from t1) x2 (r2, c3, c4), "+
							"t1 "+
							"where x1.r1 = 2 * x2.r2 and x2.c4 = t1.a");
		expectedRows = new String[][]{{"100", "20", "200"}, {"200", "40", "400"}};
		JDBC.assertFullResultSet(rs, expectedRows);

		// Check that flattening does not happen when a window is used in a
		// subquery

		rs = s.executeQuery("select * from t5 o where o.a in " +
							"(select x + row_number() over () from t2)");
		expectedRows = new String[][]{{"2", "4"},
									  {"4", "4"}};
		JDBC.assertFullResultSet(rs, expectedRows);

        // Subquery in SELECT list. DERBY-5954
        rs = s.executeQuery(
            "SELECT rn_t1, (" +
            "     SELECT rn_t2 FROM (" +
            "         SELECT row_number() over() as rn_t2 FROM t2) " +
            "         as T_2" +
            "         where T_2.rn_t2 = T_1.rn_t1 + 1  ) " +
            "     as rn_outer" +
            "     FROM (SELECT row_number() over() as rn_t1 from t2) as T_1");

        expectedRows = new String[][]{{"1", "2"},
                                      {"2", "3"},
                                      {"3", "4"},
                                      {"4", "5"},
                                      {"5", null}};
        JDBC.assertFullResultSet(rs, expectedRows);


		/*
		 * Group by and having
		 */
		rs = s.executeQuery("select r from (select a, row_number() over() as r, b from t1) x group by r");
		expectedRows = new String[][]{{"1"}, {"2"}, {"3"}, {"4"}, {"5"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select * from (select a, row_number() over() as r, b from t1) x group by a, b, r");
		expectedRows = new String[][]{{"10", "1", "100"}, 
										{"20", "2", "200"},
										{"30", "3", "300"},
										{"40", "4", "400"},
										{"50", "5", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select * from (select a, row_number() over() as r, b from t1) x group by b, r, a");
		expectedRows = new String[][]{{"10", "1", "100"}, 
										{"20", "2", "200"},
										{"30", "3", "300"},
										{"40", "4", "400"},
										{"50", "5", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select * from "+
							"(select a, row_number() over() as r, b from t1) x "+
							"group by b, r, a "+
							"having r > 2");
		expectedRows = new String[][]{{"30", "3", "300"},
										{"40", "4", "400"}, 
										{"50", "5", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select * from "+
							"(select a, row_number() over() as r, b from t1) x "+
							"group by b, r, a "+
							"having r > 2 and a >=30 "+
							"order by a desc");
		expectedRows = new String[][]{{"50", "5", "500"},
										{"40", "4", "400"}, 
										{"30", "3", "300"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		 
		rs = s.executeQuery("select * from "+
							"(select a, row_number() over() as r, b from t1) x "+
							"group by b, r, a "+
							"having r > 2 and a >=30 "+
							"order by r desc");
		expectedRows = new String[][]{{"50", "5", "500"},
										{"40", "4", "400"}, 
										{"30", "3", "300"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery("select * from "+
							"(select a, row_number() over() as r, b from t1) x "+
							"group by b, r, a "+
							"having r > 2 and a >=30 "+
							"order by a asc, r desc");
		expectedRows = new String[][]{{"30", "3", "300"},
										{"40", "4", "400"}, 
										{"50", "5", "500"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		/* A couple of distinct queries */
		rs = s.executeQuery("select distinct row_number() over (), 'ABC' from t1");
		expectedRows = new String[][]{{"1", "ABC"},
										{"2", "ABC"},
										{"3", "ABC"},
										{"4", "ABC"},
										{"5", "ABC"}};
		JDBC.assertFullResultSet(rs, expectedRows);
		
		rs = s.executeQuery(
			"select * from (select distinct row_number() over (), " +
			"                               'ABC' from t1) tmp");
		expectedRows = new String[][]{{"1", "ABC"},
										{"2", "ABC"},
										{"3", "ABC"},
										{"4", "ABC"},
										{"5", "ABC"}};
		JDBC.assertFullResultSet(rs, expectedRows);

        // Test explicitly declared window
        rs = s.executeQuery(
            "select * from (select distinct row_number() over w, 'ABC' " +
                            "from t1 window w as ()) tmp");
        JDBC.assertFullResultSet(rs, expectedRows);

        // DERBY-3634 Cannot use row_number() in ORDER BY clause
        rs = s.executeQuery(
            "select row_number() over () r, a from t1 order by r desc");
        expectedRows = new String[][]{{"5", "50"},
                                      {"4", "40"},
                                      {"3", "30"},
                                      {"2", "20"},
                                      {"1", "10"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        rs = s.executeQuery(
            "select a from t1 order by row_number() over () desc");
        expectedRows = new String[][]{{"50"},
                                      {"40"},
                                      {"30"},
                                      {"20"},
                                      {"10"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        // Another case mentioned in DERBY-3634
        rs = s.executeQuery("select a, row_number() over () from t1 except " +
                            "select a, row_number() over () from t1");
        JDBC.assertEmpty(rs);

        // And yet another case mentioned in DERBY-3634 This actually also
        // tests that ROW_NUMBER in an ORDER BY does not get optimized away if
        // there is a restriction, see SelectNode's call to
        // orderByList.removeConstantColumns(wherePredicates).
        rs = s.executeQuery("select abs(a), row_number() over () c " +
                            "from t1 where a > 30 and a <= 50 " +
                            "order by c desc");
        expectedRows = new String[][]{{"50", "2"},
                                      {"40", "1"}};
        JDBC.assertFullResultSet(rs, expectedRows);


        // DERBY-3635 Cannot build SELECT LIST expressions involving
        // ROW_NUMBER()
        rs = s.executeQuery("select row_number() over () + 10, a from t1 " +
                            "order by row_number() over () desc");
        expectedRows = new String[][]{{"15", "50"},
                                      {"14", "40"},
                                      {"13", "30"},
                                      {"12", "20"},
                                      {"11", "10"}};
        JDBC.assertFullResultSet(rs, expectedRows);


        // Check that a non-used window doesn't cause problems
        rs = s.executeQuery("select a from t1 window r as () order by a desc");
        expectedRows = new String[][]{{"50"},
                                      {"40"},
                                      {"30"},
                                      {"20"},
                                      {"10"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        // Check that a row_number combined with group by works. Good to test
        // this since windowing uses a similar rewrite mechanism to group by
        // and could interfere (seen during development).
        rs = s.executeQuery("select row_number() over r, b, sum(a) from t5 " +
                            "group by b window r as ()");
        expectedRows = new String[][]{{"1", "1", "1"},
                                      {"2", "4", "9"},
                                      {"3", "9", "5"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        // Check that a row_number combined with group by works. Good to test
        // this since windowing uses a similar rewrite mechanism to group by
        // and could interfere (seen during development).
        rs = s.executeQuery("select row_number() over r, b, sum(a) from t5 " +
                            "group by b window r as ()");
        expectedRows = new String[][]{{"1", "1", "1"},
                                      {"2", "4", "9"},
                                      {"3", "9", "5"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        rs = s.executeQuery("select b, sum(a) from t5 " +
                            "group by b window r as ()");
        expectedRows = new String[][]{{"1", "1"},
                                      {"4", "9"},
                                      {"9", "5"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        // Check that row_number inside EXISTS works.
        rs = s.executeQuery("SELECT * FROM t2 WHERE EXISTS " +
                            "(SELECT ROW_NUMBER() OVER () FROM t5)");
        expectedRows = new String[][]{{"1"},{"2"},{"3"},{"4"},{"5"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        /*
         * Negative testing of Statements
         */

        // Missing required OVER ()
        assertStatementError(
            LANG_SYNTAX_ERROR, s,
			"select row_number() as r, * from t1 where t1.a > 2");

        // Illegal where clause, r not a named column of t1.
        assertStatementError(
            LANG_COLUMN_NOT_FOUND, s,
            "select row_number() over () as r, a from t1 where r < 3");

        // Illegal use of asterisk with another column identifier.
        assertStatementError(
            LANG_SYNTAX_ERROR, s,
            "select row_number() over () as r, * from t1 where t1.a > 2");

        // Order by in window specification
        assertStatementError(
            NOT_IMPLEMENTED,
            s,
            "select row_number() over (order by i) as r from t1");

        // Other window function than row_number:
        assertStatementError(NOT_IMPLEMENTED,
                             s,
                             "select max(i) over () from t1");

        // Illegal context: WHERE, cf. SQL 2003, section 7.8 SR 2
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
                             s,
                             "select * from t4 where row_number() over () > 3");

        // But nested inside a subquery it should work:
        rs = s.executeQuery("select * from t2 where x in " +
                            "     (select row_number() over () from t4)");
        expectedRows = new String[][]{{"1"},{"2"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        // Illegal context: GROUP BY
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
                             s,
                             "select * from t4 group by row_number() over ()");

        // But nested inside a subquery it should work.
        // Fails: DERBY-4403, enable this test when that issue is fixed.
        // rs = s.executeQuery("select * from t4 group by a + " +
        //             "(select row_number() over () from t4 where a=10)");
        // JDBC.assertEmpty(rs);


        // Illegal context: HAVING, cf. SQL 2003, section 7.10 SR 4
        assertStatementError(
			LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
			s,
			"select * from t4 group by a having b = row_number() over ()");

        // But nested inside a subquery it should work:
        rs = s.executeQuery(
            "select sum(a) from t5 group by b " +
            "   having b = (select row_number() over () + 3 " +
            "                   from t5 where a=1)");
        expectedRows = new String[][]{{"9"}};


        // Illegal context: VALUES
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
                             s,
                             "values row_number() over ()");

        // But nested inside a subquery it should work:
        rs = s.executeQuery("values 3 + " +
                            "(select row_number() over () from t2 where x=1)");
        expectedRows = new String[][]{{"4"}};
        JDBC.assertFullResultSet(rs, expectedRows);

        // Illegal context: Aggregate function, cf. SQL 2003, section 10.9 SR 7
        // a)
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
                             s,
                             "select sum(row_number() over ()) from t4");

        // Illegal context: JOIN ON clause, cf. SQL 2003, section 7.7 SR 5
        assertStatementError(
            LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
            s,
            "select * from t4 t_1 join t4 t_2 on " +
            "                     t_1.a = row_number() over () + t_2.a");

        // DERBY-6565: NPE before
        assertStatementError(
                LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
                s,
                "update t3 set y = y - row_number() over ()");

        // DERBY-6688: subquery using SubqueryNode rather than FromSubquery
        // had problems with presence of window function in order by.

        JDBC.assertFullResultSet(s.executeQuery("select * from t3"),
                new String[][]{{"4"},{"5"},{"6"},{"7"},{"8"}});

        // failed prior to DERBY-6688
        s.executeUpdate(
            "update t3 set y = y - " +
            "    (select y from t3 order by row_number() over () " +
            "     fetch first 1 row only)");
        JDBC.assertFullResultSet(s.executeQuery("select * from t3"),
                new String[][]{{"0"},{"1"},{"2"},{"3"},{"4"}});

        // Used to work before
        JDBC.assertFullResultSet(s.executeQuery(
            "select * from  " +
            "    (select y from t3 order by row_number() over () " + 
            "     fetch first 1 row only) tt"),
            new String[][]{{"0"}});

        // failed prior to DERBY-6688
        JDBC.assertFullResultSet(s.executeQuery(
            "select * from t3 where y = " +
            "    (select y from t3 order by row_number() over () " + 
            "     fetch first row only)"),
            new String[][]{{"0"}});

        // DERBY-6689: ArrayIndexOutOfBoundsException before
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
            s,
            "merge into t2 using t3 on (t2.x=t3.y) when not matched then " + 
            "    insert values (row_number() over ())");
        
        // DERBY-6691: NPE before
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
            s,
            "call syscs_util.syscs_compress_table(" + 
            "    'APP', 'T2', row_number() over ())");

        // DERBY-6690: a window function in generated clause was not detected
        // before
        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
            s,
            "create table t (x int generated always as " +
            "    (row_number() over ()))");

        assertStatementError(LANG_WINDOW_FUNCTION_CONTEXT_ERROR,
            s,
            "alter table t2 add column foo int generated always as " +
            "    (row_number() over ())");
    }


    public void testMetaData()
        throws SQLException {

        if (JDBC.vmSupportsJSR169()) {
            // does not support metadata
            return;
        }

        Statement s = createStatement();
        ResultSet rs = s.executeQuery(
            "select row_number() over () from sys.systables");
        ResultSetMetaData rsmd = rs.getMetaData();

        assertEquals(ResultSetMetaData.columnNoNulls, rsmd.isNullable(1));
    }


    private String makeString(int len)
    {
        StringBuffer buf = new StringBuffer(len);
        for (int i = 0; i < len; i++)
            buf.append('a');
        return buf.toString();
    }
        /**
          * Basic test of GROUP BY ROLLUP capability.
          *
          * This test case has a few basic tests of GROUP BY ROLLUP, both
          * positive and negative tests.
          */
    public void testGroupByRollup()
        throws SQLException
    {
        Statement s = createStatement();
        s.executeUpdate("create table ru (a int, b int, c int, d int)");
        s.executeUpdate("insert into ru values (1,1,1,1), (1,2,3,4),"+
                "(1,1,2,2), (4,3,2,1), (4,4,4,4)");
        JDBC.assertUnorderedResultSet( s.executeQuery(
                    "select a,b,c,sum(d) from ru group by rollup(a,b,c)"),
                new String[][]{
                    {"1","1","1","1"},
                    {"1","1","2","2"},
                    {"1","2","3","4"},
                    {"4","3","2","1"},
                    {"4","4","4","4"},
                    {"1","1",null,"3"},
                    {"1","2",null,"4"},
                    {"4","3",null,"1"},
                    {"4","4",null,"4"},
                    {"1",null,null,"7"},
                    {"4",null,null,"5"},
                    {null,null,null,"12"}});
        JDBC.assertFullResultSet( s.executeQuery(
                "select count(*) from ru group by mod(a,b)"),
                new String[][]{ {"3"},{"2"}});

        // Try a few negative tests:
        assertStatementError("42X04", s,
                "select a,b,c,sum(d) from ru group by rollup");
        assertStatementError("42X01", s,
                "select a,b,c,sum(d) from ru group by rollup(");
        assertStatementError("42X01", s,
                "select a,b,c,sum(d) from ru group by rollup)");
        assertStatementError("42X01", s,
                "select a,b,c,sum(d) from ru group by rollup()");

        s.executeUpdate("drop table ru");
        s.close();
    }
    /**
      * Verify that ROLLUP can still be used as the name of a column or table.
      */
    public void testRollupReservedWord()
        throws SQLException
    {
        Statement s = createStatement();
        s.executeUpdate("create table t_roll(rollup int, x int)");
        JDBC.assertEmpty( s.executeQuery(
                    "select rollup, sum(x) from t_roll group by rollup"));
        JDBC.assertEmpty( s.executeQuery(
                    "select count(*) from t_roll group by mod(rollup,x)"));
        JDBC.assertEmpty( s.executeQuery(
                    "select count(*) from t_roll group by mod(x,rollup)"));
        s.executeUpdate("create table rollup(a int, x int)");
        JDBC.assertEmpty( s.executeQuery("select a, x from rollup"));
        s.executeUpdate("insert into rollup(a,x) values(1,2)");
        JDBC.assertUnorderedResultSet( s.executeQuery(
                    "select a,sum(x) from rollup group by rollup(a)"),
                new String[][]{
                    {"1","2"}, {null,"2"}});
        s.executeUpdate("drop table rollup");
        s.executeUpdate("drop table t_roll");
        s.close();
    }
    /**
      * Verify that non-aggregate columns are returned as NULLABLE if ROLLUP.
      *
      * If a GROUP BY ROLLUP is used, the un-aggregated columns may contain
      * NULL values, so we need to verify that the DatabaseMetadata returns
      * the right values for the nullability of the columns.
      */
    public void testRollupColumnNullability()
        throws SQLException
    {
        Statement s = createStatement();
        s.executeUpdate(
                "create table t_notnull(a int not null, b int," +
                "                       c int not null, d int)");
        ResultSet rs = s.executeQuery(
                "select a,b,c,sum(d) from t_notnull group by rollup(a,b,c)");
        JDBC.assertNullability(rs,
                new boolean[]{true, true, true, true});
        rs.close();

        rs = s.executeQuery(
                "select 1,2,3,sum(d) from t_notnull group by rollup(1,2,3)");
        JDBC.assertNullability(rs,
                new boolean[]{true, true, true, true});
        rs.close();

        s.executeUpdate("drop table t_notnull");
        s.close();
    }

    /**
      * Verify the behavior of GROUP BY ROLLUP for empty result sets.
      */
    public void testRollupEmptyTables()
        throws SQLException
    {
        Statement s = createStatement();
        s.executeUpdate("create table ru (a int, b int, c int, d int)");

        JDBC.assertEmpty( s.executeQuery("select sum(a) from ru group by b"));
        JDBC.assertSingleValueResultSet(
                s.executeQuery("select sum(a) from ru"), (String)null);
        s.executeUpdate("insert into ru values (1,1,1,1), (1,2,3,4),"+
                "(1,1,2,2), (4,3,2,1), (4,4,4,4)");
        JDBC.assertEmpty( s.executeQuery(
                    "select b, sum(a) from ru where 1<>1 group by rollup(b)"));

        s.executeUpdate("drop table ru");
        s.close();
    }

    /**
      * A ROLLUP case suggested by Dag in 1-sep-2009 comment on DERBY-3002
      */
    public void testRollupNullabilityCasts()
        throws SQLException
    {
        Statement s = createStatement();
        s.executeUpdate("create table t(c varchar(2) not null," +
                "c2 varchar(2), i integer)");
        s.executeUpdate("insert into t values('aa',null,null)");
        s.executeUpdate("insert into t values('bb',null,null)");
        String [][]rows = 
                new String[][]{
                    {"aa",null,null},
                    {"aa",null,null},
                    {null,null,null},
                    {"bb",null,null},
                    {"bb",null,null}};
        JDBC.assertUnorderedResultSet(
            s.executeQuery("select c,c2,sum(i) from t group by rollup(c,c2)"),
            rows);
        JDBC.assertUnorderedResultSet(s.executeQuery(
                "select cast(c as varchar(2)),c2,sum(i) from t " +
                "group by rollup(c,c2)"),
            rows);
        /* FIXME -- this test currently fails due to improper handling of
           the nullability of the result columns.
        JDBC.assertUnorderedResultSet(s.executeQuery(
                "select cast(x as varchar(2)),y,z from " +
                " (select c,c2,sum(i) from t " +
                "      group by rollup (c,c2)) t(x,y,z)"),
            rows);
            */

        s.executeUpdate("drop table t");
        s.close();
    }

    /**
      * Verify the behavior of GROUP BY ROLLUP when it can use a covering index.
      */
    public void testRollupOfCoveringIndex()
        throws SQLException
    {
        Statement s = createStatement();
        s.executeUpdate("create table ru (a int,b int,c int,d varchar(1000))");
        s.executeUpdate("create index ru_idx on ru(a,b,c)");
        PreparedStatement ps = prepareStatement(
                "insert into ru (a,b,c,d) values (?,?,?,?)");
        for (int i = 0; i < 100; i++)
        {
            ps.setInt(1, (i%5));
            ps.setInt(2, 2*i);
            ps.setInt(3, 100+i);
            ps.setString(4, makeString(900));
            ps.executeUpdate();
        }
        ps.close();
        // FIXME
        //dumpIt(s, 2, "select a,sum(c) from ru group by a");
        //dumpIt(s, 3, "select a,b,sum(c) from ru group by a,b");
        //dumpIt(s, 3, "select a,b,sum(c) from ru group by rollup(a,b)");
        s.executeUpdate("drop table ru");
        s.close();
    }
    private void dumpIt(Statement s, int cols, String sql)
        throws SQLException
    {
        System.out.println(sql);
        ResultSet rs = s.executeQuery(sql);
        while (rs.next())
        {
            StringBuffer buf = new StringBuffer();
            for (int i = 1; i <= cols; i++)
            {
                if (i > 1)
                    buf.append(",");
                buf.append(rs.getString(i));
            }
            System.out.println(buf.toString());
        }
        rs.close();
    }
    /*
     * Various GROUP BY tests, with and without ROLLUP.
     */
    public void testGroupByWithAndWithoutRollup()
        throws SQLException
    {
        Statement s = createStatement();
        // A very simple set of master-detail ORDER and ORDER_ITEM tables,
        // with some fake customer data:
        s.executeUpdate(
                "create table orders(order_id int primary key," +
                "   customer varchar(10)," +
                "   order_date date, " +
                "   shipping int)");
        s.executeUpdate(
                "create table order_items(item_id int primary key," +
                "   order_id int," +
                "   order_item varchar(10), " +
                "   cost int)");
        s.executeUpdate(
                "create table customers(customer varchar(10) primary key," +
                "   name varchar(100), city varchar(100), state varchar(2))");
        s.executeUpdate("insert into customers values " +
                "('ABC','ABC Corporation','ABC City', 'AB')," +
                "('DEF','DEF, Inc.', 'DEFburg', 'DE')");
        s.executeUpdate("insert into orders values(1,'ABC','2009-01-01',40)");
        s.executeUpdate("insert into orders values(2,'ABC','2009-01-02',30)");
        s.executeUpdate("insert into orders values(3,'ABC','2009-01-03',25)");
        s.executeUpdate("insert into orders values(4,'DEF','2009-01-02',10)");
        s.executeUpdate("insert into order_items values(1,1,'Item A',100)");
        s.executeUpdate("insert into order_items values(2,1,'Item B',150)");
        s.executeUpdate("insert into order_items values(3,2,'Item C',125)");
        s.executeUpdate("insert into order_items values(4,2,'Item B',50)");
        s.executeUpdate("insert into order_items values(5,2,'Item H',200)");
        s.executeUpdate("insert into order_items values(6,3,'Item X',100)");
        s.executeUpdate("insert into order_items values(7,4,'Item Y',50)");
        s.executeUpdate("insert into order_items values(8,4,'Item Z',300)");
        // Joining the two tables produces one row per order item:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.order_id, o.customer, o.order_date, " +
            "o.shipping, od.item_id, od.order_item, od.cost " +
            " from orders o inner join order_items od " +
            " on o.order_id = od.order_id"),
            new String[][]{
                    {"1","ABC","2009-01-01","40","1","Item A","100"},
                    {"1","ABC","2009-01-01","40","2","Item B","150"},
                    {"2","ABC","2009-01-02","30","3","Item C","125"},
                    {"2","ABC","2009-01-02","30","4","Item B","50"},
                    {"2","ABC","2009-01-02","30","5","Item H","200"},
                    {"3","ABC","2009-01-03","25","6","Item X","100"},
                    {"4","DEF","2009-01-02","10","7","Item Y","50"},
                    {"4","DEF","2009-01-02","10","8","Item Z","300"},
                });
        // Grouping the items by customer to compute items/customer:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","6"},
                {"DEF","2"},
            });
        // Also include the total cost per customer:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","6","725"},
                {"DEF","2","350"},
            });
        // ROLLUP the items and costs to grand totals:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by ROLLUP(o.customer)"),
            new String[][]{
                {"ABC","6","725"},
                {"DEF","2","350"},
                {null,"8","1075"},
            });
        // Show a usage of Count(distinct) to compute the orders/customer,
        // which is not the same as the items/customer:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       count(distinct o.order_id) as orders_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","6","3"},
                {"DEF","2","1"},
            });
        // ROLLUP should work for the distinct count, too:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       count(distinct o.order_id) as orders_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by ROLLUP(o.customer)"),
            new String[][]{
                {"ABC","6","3"},
                {"DEF","2","1"},
                {null,"8","4"},
            });
        // can we compute the total shipping per customer:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total, " +
            "       count(distinct o.order_id) as orders_per_customer, " +
            "       sum(o.shipping) as shipping_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","6","725","3","195"},
                {"DEF","2","350","1","20"},
            });
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total, " +
            "       count(distinct o.order_id) as orders_per_customer, " +
            "       sum(o.shipping) as shipping_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by ROLLUP(o.customer)"),
            new String[][]{
                {"ABC","6","725","3","195"},
                {"DEF","2","350","1","20"},
                {null,"8","1075","4","215"},
            });
        // Show a usage of distinct shipping aggregate, similar to the
        // distinct count aggregate:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total, " +
            "       count(distinct o.order_id) as orders_per_customer, " +
            "       sum(distinct o.shipping) as shipping_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","6","725","3","95"},
                {"DEF","2","350","1","10"},
            });
        // Demonstrate some of the dangers of using distinct aggregates.
        // Duplicate SUM values may be real duplicates from the data, not
        // from duplicate-producing master-detail joins. The COUNT changes
        // from 1 to 2 for customer DEF, but the shipping_per_customer is
        // still 10, which is logically wrong (there are 2 DEF orders, each
        // with value 10, so we "expected" 20 for shipping_per_customer).
        s.executeUpdate("insert into orders values(5,'DEF','2009-01-04',10)");
        s.executeUpdate("insert into order_items values(9,5,'Item J',125)");
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total, " +
            "       count(distinct o.order_id) as orders_per_customer, " +
            "       sum(distinct o.shipping) as shipping_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","6","725","3","95"},
                {"DEF","3","475","2","10"},
            });
        // Same as before, but with ROLLUP:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.Customer, count(*) as items_per_customer, " +
            "       sum(od.cost) as order_total, " +
            "       count(distinct o.order_id) as orders_per_customer, " +
            "       sum(distinct o.shipping) as shipping_per_customer " +
            " from orders o inner join order_items od " +
            "      on o.order_id = od.order_id " +
            " group by ROLLUP(o.customer)"),
            new String[][]{
                {"ABC","6","725","3","95"},
                {"DEF","3","475","2","10"},
                {null,"9","1200","5","105"},
            });
        // Produce the results we expected by constructing a sub-query:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select order_id, count(*) as Items_per_order, " +
            "       sum(cost) as Order_total "+
            " from order_items " +
            " group by order_id"),
            new String[][]{
                {"1","2","250"},
                {"2","3","375"},
                {"3","1","100"},
                {"4","2","350"},
                {"5","1","125"},
            });
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select order_id, count(*) as Items_per_order, " +
            "       sum(cost) as Order_total "+
            " from order_items " +
            " group by ROLLUP(order_id)"),
            new String[][]{
                {"1","2","250"},
                {"2","3","375"},
                {"3","1","100"},
                {"4","2","350"},
                {"5","1","125"},
                {null,"9","1200"},
            });
        // ... then encapsulate that sub-select with a join to the orders:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.order_id, o.Customer, o.Shipping, " +
            "       d.items_per_order, d.order_total " +
            " from orders o inner join (" +
            "   select order_id, count(*) as Items_per_order, " +
            "          sum(cost) as Order_total "+
            "    from order_items " +
            "    group by order_id " +
            "   ) d on o.order_id = d.order_id"),
            new String[][]{
                {"1","ABC","40","2","250"},
                {"2","ABC","30","3","375"},
                {"3","ABC","25","1","100"},
                {"4","DEF","10","2","350"},
                {"5","DEF","10","1","125"},
            });
        // ... and group *THAT* join, in turn, by customer, to get the
        // correct values of shipping_per_customer and items_per_customer.
        // Note that total_per_customer is a SUM(SUM()), while 
        // items_per_customer is a SUM(COUNT()). And no DISTINCT needed.
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.customer, count(*) as orders_per_customer, " +
            "       sum(o.shipping) as shipping_per_customer, " +
            "       sum(d.items_per_order) as items_per_customer, " +
            "       sum(d.order_total) as total_per_customer " +
            " from orders o inner join (" +
            "   select order_id, count(*) as Items_per_order, " +
            "          sum(cost) as Order_total "+
            "    from order_items " +
            "    group by order_id " +
            "   ) d on o.order_id = d.order_id " +
            " group by o.customer"),
            new String[][]{
                {"ABC","3","95","6","725"},
                {"DEF","2","20","3","475"},
            });
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.customer, count(*) as orders_per_customer, " +
            "       sum(o.shipping) as shipping_per_customer, " +
            "       sum(d.items_per_order) as items_per_customer, " +
            "       sum(d.order_total) as total_per_customer " +
            " from orders o inner join (" +
            "   select order_id, count(*) as Items_per_order, " +
            "          sum(cost) as Order_total "+
            "    from order_items " +
            "    group by order_id " +
            "   ) d on o.order_id = d.order_id " +
            " group by ROLLUP(o.customer)"),
            new String[][]{
                {"ABC","3","95","6","725"},
                {"DEF","2","20","3","475"},
                {null,"5","115","9","1200"},
            });
        // Include customer address information. First by joining and grouping:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select o.customer, c.name, c.city, c.state, " +
            "       count(*) as orders_per_customer, " +
            "       sum(o.shipping) as shipping_per_customer, " +
            "       sum(d.items_per_order) as items_per_customer, " +
            "       sum(d.order_total) as total_per_customer " +
            " from orders o inner join (" +
            "   select order_id, count(*) as Items_per_order, " +
            "          sum(cost) as Order_total "+
            "    from order_items " +
            "    group by order_id " +
            "   ) d on o.order_id = d.order_id " +
            "   inner join customers c on o.customer = c.customer " +
            " group by ROLLUP(o.customer,c.name, c.city,c.state)"),
            new String[][]{
                {"ABC","ABC Corporation","ABC City","AB","3","95","6","725"},
                {"DEF","DEF, Inc.","DEFburg","DE","2","20","3","475"},
                {"ABC","ABC Corporation","ABC City",null,"3","95","6","725"},
                {"DEF","DEF, Inc.","DEFburg",null,"2","20","3","475"},
                {"ABC","ABC Corporation",null,null,"3","95","6","725"},
                {"DEF","DEF, Inc.",null,null,"2","20","3","475"},
                {"ABC",null,null,null,"3","95","6","725"},
                {"DEF",null,null,null,"2","20","3","475"},
                {null,null,null,null,"5","115","9","1200"},
            });
        // Then, alternately, by sub-selecting and grouping:
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select c.customer, c.name, c.city, c.state, " +
            "       o.orders_per_customer, o.shipping_per_customer, " +
            "       o.items_per_customer, o.total_per_customer " +
            " from ( " +
            "   select o.customer, count(*) as orders_per_customer, " +
            "          sum(o.shipping) as shipping_per_customer, " +
            "          sum(d.items_per_order) as items_per_customer, " +
            "          sum(d.order_total) as total_per_customer " +
            "    from orders o inner join (" +
            "      select order_id, count(*) as Items_per_order, " +
            "             sum(cost) as Order_total "+
            "       from order_items " +
            "       group by order_id " +
            "      ) d on o.order_id = d.order_id " +
            "    group by o.customer) o " +
            "  inner join customers c on o.customer = c.customer"),
            new String[][]{
                {"ABC","ABC Corporation","ABC City","AB","3","95","6","725"},
                {"DEF","DEF, Inc.","DEFburg","DE","2","20","3","475"},
            });
        // Note that we can put the ROLLUP in the sub-query, but then we
        // need to outer-join with the customers table since the rollup
        // results will have NULL in the join key.
        JDBC.assertUnorderedResultSet( s.executeQuery(
            "select c.customer, c.name, c.city, c.state, " +
            "       o.orders_per_customer, o.shipping_per_customer, " +
            "       o.items_per_customer, o.total_per_customer " +
            " from ( " +
            "   select o.customer, count(*) as orders_per_customer, " +
            "          sum(o.shipping) as shipping_per_customer, " +
            "          sum(d.items_per_order) as items_per_customer, " +
            "          sum(d.order_total) as total_per_customer " +
            "    from orders o inner join (" +
            "      select order_id, count(*) as Items_per_order, " +
            "             sum(cost) as Order_total "+
            "       from order_items " +
            "       group by order_id " +
            "      ) d on o.order_id = d.order_id " +
            "    group by ROLLUP(o.customer)) o " +
            "  left outer join customers c on o.customer = c.customer"),
            new String[][]{
                {"ABC","ABC Corporation","ABC City","AB","3","95","6","725"},
                {"DEF","DEF, Inc.","DEFburg","DE","2","20","3","475"},
                {null,null,null,null,"5","115","9","1200"},
            });

        s.close();
    }
}
