/*
 *
 * Derby - Class org.apache.derbyTesting.functionTests.tests.lang.XMLConcurrencyTest
 *
 * 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.Connection;
import java.sql.PreparedStatement;
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;
import org.apache.derbyTesting.junit.XML;

/**
 * Test that XML operators can be invoked by multiple threads concurrently.
 * Regression test case for DERBY-3870.
 */
public class XMLConcurrencyTest extends BaseJDBCTestCase {

    /** Create an instance of this test case. */
    public XMLConcurrencyTest(String name) {
        super(name);
    }

    /** Create a suite of all test cases in this class. */
    public static Test suite() {
        // XML operators are engine functionality, so run this test in
        // embedded mode only.
        if (XML.classpathMeetsXMLReqs()) {
            return new CleanDatabaseTestSetup(
                TestConfiguration.embeddedSuite(XMLConcurrencyTest.class)) {
                    protected void decorateSQL(Statement s)
                            throws SQLException {
                        createTestTable(s);
                    }
                };
        } else {
            return new BaseTestSuite("XMLConcurrencyTest - empty");
        }
    }

    /**
     * Start four threads that execute queries that use all the XML operators.
     * If each thread manages 100 iterations without failing, running
     * concurrently with the other threads, the test case passes.
     */
    public void testConcurrency() throws Exception {
        WorkerThread[] allThreads = new WorkerThread[4];
        for (int i = 0; i < allThreads.length; i++) {
            allThreads[i] = new WorkerThread(openDefaultConnection(), 100);
            allThreads[i].start();
        }

        for (int i = 0; i < allThreads.length; i++) {
            allThreads[i].join();
            Throwable t = allThreads[i].throwable;
            if (t != null) {
                fail("Worker thread failed", t);
            }
        }
    }

    /**
     * A thread class that does the actual work in the test.
     */
    private class WorkerThread extends Thread {
        final Connection conn;
        final int iterations;
        Throwable throwable;

        WorkerThread(Connection conn, int iterations) {
            this.conn = conn;
            this.iterations = iterations;
        }

        public void run() {
            try {
                runXMLTest(conn, iterations);
            } catch (Throwable t) {
                throwable = t;
            }
        }
    }

    /**
     * <p>
     * Create a table with test data. The table contains three columns:
     * <p>
     *
     * <ol>
     * <li>
     * An ID column used to identify the rows and to give a stable ordering.
     * </li>
     * <li>
     * A VARCHAR column holding the string representation of an XML document.
     * </li>
     * <li>
     * An XML column holding the XML representation of the document in the
     * VARCHAR column.
     * </li>
     * </ol>
     */
    private static void createTestTable(Statement s) throws SQLException {
        s.executeUpdate("create table t (id int primary key " +
                "generated always as identity, vc varchar(100), " +
                "x generated always as " +
                "(xmlparse(document vc preserve whitespace)))");

        PreparedStatement ins = s.getConnection().prepareStatement(
                "insert into t(vc) values ?");

        String[] docs = {
            "<doc><a x='1'>abc</a><b x='2'>def</b></doc>",
            "<doc><a x='2'>abc</a><b x='3'>def</b></doc>",
            "<doc/>",
            "<a/>",
            null,
        };

        for (int i = 0; i < docs.length; i++) {
            ins.setString(1, docs[i]);
            ins.executeUpdate();
        }

        ins.close();
    }

    /**
     * Do the work for one of the worker threads. Perform queries that use
     * all the XML operators. Repeat the queries the specified number of times.
     *
     * @param conn the connection on which to execute the queries
     * @param iterations the number of times each query should be executed
     */
    private static void runXMLTest(Connection conn, int iterations)
            throws SQLException {
        // Query that tests XMLQUERY and XMLSERIALIZE. Count the number of
        // nodes with an attribute named x with a value greater than 1.
        PreparedStatement ps1 = conn.prepareStatement(
            "select id, xmlserialize(" +
            "xmlquery('count(//*[@x>1])' passing by ref x empty on empty) " +
            "as varchar(100)) from t order by id");

        String[][] expected1 = {
            {"1", "1"}, {"2", "2"}, {"3", "0"}, {"4", "0"}, {"5", null}
        };

        // Query that tests XMLEXISTS. Find all documents containing a "doc"
        // node with a nested "a" node whose x attribute is 2.
        PreparedStatement ps2 = conn.prepareStatement(
            "select id from t where " +
            "xmlexists('/doc/a[@x=2]' passing by ref x) " +
            "order by id");

        String expected2 = "2";

        // Query that tests XMLPARSE and XMLSERIALIZE.
        PreparedStatement ps3 = conn.prepareStatement(
            "select count(*) from t where " +
            "xmlserialize(xmlparse(document vc preserve whitespace) " +
            "as varchar(100)) = " +
            "xmlserialize(x as varchar(100))");

        String expected3 = "4";

        for (int i = 0; i < iterations; i++) {
            JDBC.assertFullResultSet(ps1.executeQuery(), expected1);
            JDBC.assertSingleValueResultSet(ps2.executeQuery(), expected2);
            JDBC.assertSingleValueResultSet(ps3.executeQuery(), expected3);
        }

        ps1.close();
        ps2.close();
        ps3.close();
        conn.close();
    }
}
