/*
 Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
 

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

  This program is free software; you can redistribute it and/or modify it under the terms
  of the GNU General Public License as published by the Free Software Foundation; version 2
  of the License.

  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA



 */
package testsuite.perf;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.NumberFormat;

import testsuite.BaseTestCase;

/**
 * Simple performance testing unit test.
 * 
 * @author Mark Matthews
 */
public class LoadStorePerfTest extends BasePerfTest {
	/** The table type to use (only for MySQL), 'HEAP' by default */
	private String tableType = "HEAP";

	private boolean takeMeasurements = false;

	private boolean useColumnNames = false;

	private boolean largeResults = false;

	/**
	 * Constructor for LoadStorePerfTest.
	 * 
	 * @param name
	 *            the name of the test to run
	 */
	public LoadStorePerfTest(String name) {
		super(name);

		String newTableType = System
				.getProperty("com.mysql.jdbc.test.tabletype");

		this.largeResults = "TRUE"
				.equalsIgnoreCase(System
						.getProperty("com.mysql.jdbc.testsuite.loadstoreperf.useBigResults"));

		if ((newTableType != null) && (newTableType.length() > 0)) {
			this.tableType = newTableType;

			System.out.println("Using specified table type of '"
					+ this.tableType + "'");
		}
	}

	/**
	 * Runs all tests in this test case
	 * 
	 * @param args
	 *            ignored
	 * 
	 * @throws Exception
	 *             if an error occurs
	 */
	public static void main(String[] args) throws Exception {
		new LoadStorePerfTest("test1000Transactions").run();
	}

	/**
	 * @see junit.framework.TestCase#setUp()
	 */
	public void setUp() throws Exception {
		super.setUp();

		try {
			this.stmt.executeUpdate("DROP TABLE perfLoadStore");
		} catch (SQLException sqlEx) {
			// ignore
		}

		String dateTimeType = "DATETIME";

		if (BaseTestCase.dbUrl.indexOf("oracle") != -1) {
			dateTimeType = "TIMESTAMP";
		}

		//
		// Approximate a run-of-the-mill entity in a business application
		//
		String query = "CREATE TABLE perfLoadStore (priKey INT NOT NULL, "
				+ "fk1 INT NOT NULL, " + "fk2 INT NOT NULL, " + "dtField "
				+ dateTimeType + ", " + "charField1 CHAR(32), "
				+ "charField2 CHAR(32), " + "charField3 CHAR(32), "
				+ "charField4 CHAR(32), " + "intField1 INT, "
				+ "intField2 INT, " + "intField3 INT, " + "intField4 INT, "
				+ "doubleField1 DECIMAL," + "doubleField2 DOUBLE,"
				+ "doubleField3 DOUBLE," + "doubleField4 DOUBLE,"
				+ "PRIMARY KEY (priKey))";

		if (BaseTestCase.dbUrl.indexOf("mysql") != -1) {
			query += (getTableTypeDecl() + " =" + this.tableType);
		}

		this.stmt.executeUpdate(query);

		String currentDateValue = "NOW()";

		if (BaseTestCase.dbUrl.indexOf("sqlserver") != -1) {
			currentDateValue = "GETDATE()";
		}

		if (BaseTestCase.dbUrl.indexOf("oracle") != -1) {
			currentDateValue = "CURRENT_TIMESTAMP";
		}

		int numLoops = 1;

		if (this.largeResults) {
			numLoops = 32;
		}

		System.out.println("Inserting " + numLoops + " rows to retrieve...");

		for (int i = 0; i < numLoops; i++) {
			this.stmt.executeUpdate("INSERT INTO perfLoadStore (" + "priKey, "
					+ "fk1, " + "fk2, " + "dtField, " + "charField1, "
					+ "charField2, " + "charField3, " + "charField4, "
					+ "intField1, " + "intField2, " + "intField3, "
					+ "intField4, " + "doubleField1," + "doubleField2,"
					+ "doubleField3," + "doubleField4" + ") VALUES (" + i + "," // priKey
					+ "2," // fk1
					+ "3," // fk2
					+ currentDateValue + "," // dtField
					+ "'0123456789ABCDEF0123456789ABCDEF'," // charField1
					+ "'0123456789ABCDEF0123456789ABCDEF'," // charField2
					+ "'0123456789ABCDEF0123456789ABCDEF'," // charField3
					+ "'0123456789ABCDEF0123456789ABCDEF'," // charField4
					+ "7," // intField1
					+ "8," // intField2
					+ "9," // intField3
					+ "10," // intField4
					+ "1.20," // doubleField1
					+ "2.30," // doubleField2
					+ "3.40," // doubleField3
					+ "4.50" // doubleField4
					+ ")");
		}
	}

	/**
	 * @see junit.framework.TestCase#tearDown()
	 */
	public void tearDown() throws Exception {
		try {
			this.stmt.executeUpdate("DROP TABLE perfLoadStore");
		} catch (SQLException sqlEx) {
			// ignore
		}

		super.tearDown();
	}

	/**
	 * Tests and times 1000 load/store type transactions
	 * 
	 * @throws Exception
	 *             if an error occurs
	 */
	public void test1000Transactions() throws Exception {
		this.takeMeasurements = false;
		warmUp();
		this.takeMeasurements = true;
		doIterations(29);

		reportResults("\n\nResults for instance # 1: ");
	}

	/**
	 * Runs one iteration of the test.
	 * 
	 * @see testsuite.perf.BasePerfTest#doOneIteration()
	 */
	protected void doOneIteration() throws Exception {
		PreparedStatement pStmtStore = this.conn
				.prepareStatement("UPDATE perfLoadStore SET " + "priKey = ?, "
						+ "fk1 = ?, " + "fk2 = ?, " + "dtField = ?, "
						+ "charField1 = ?, " + "charField2 = ?, "
						+ "charField3 = ?, " + "charField4 = ?, "
						+ "intField1 = ?, " + "intField2 = ?, "
						+ "intField3 = ?, " + "intField4 = ?, "
						+ "doubleField1 = ?," + "doubleField2 = ?,"
						+ "doubleField3 = ?," + "doubleField4 = ?"
						+ " WHERE priKey=?");
		PreparedStatement pStmtCheck = this.conn
				.prepareStatement("SELECT COUNT(*) FROM perfLoadStore WHERE priKey=?");
		PreparedStatement pStmtLoad = null;

		if (this.largeResults) {
			pStmtLoad = this.conn.prepareStatement("SELECT " + "priKey, "
					+ "fk1, " + "fk2, " + "dtField, " + "charField1, "
					+ "charField2, " + "charField3, " + "charField4, "
					+ "intField1, " + "intField2, " + "intField3, "
					+ "intField4, " + "doubleField1," + "doubleField2, "
					+ "doubleField3," + "doubleField4" + " FROM perfLoadStore");
		} else {
			pStmtLoad = this.conn.prepareStatement("SELECT " + "priKey, "
					+ "fk1, " + "fk2, " + "dtField, " + "charField1, "
					+ "charField2, " + "charField3, " + "charField4, "
					+ "intField1, " + "intField2, " + "intField3, "
					+ "intField4, " + "doubleField1," + "doubleField2, "
					+ "doubleField3," + "doubleField4"
					+ " FROM perfLoadStore WHERE priKey=?");
		}

		NumberFormat numFormatter = NumberFormat.getInstance();
		numFormatter.setMaximumFractionDigits(4);
		numFormatter.setMinimumFractionDigits(4);

		int transactionCount = 5000;

		if (this.largeResults) {
			transactionCount = 50;
		}

		long begin = System.currentTimeMillis();

		for (int i = 0; i < transactionCount; i++) {
			this.conn.setAutoCommit(false);
			pStmtCheck.setInt(1, 1);
			this.rs = pStmtCheck.executeQuery();

			while (this.rs.next()) {
				this.rs.getInt(1);
			}

			this.rs.close();

			if (!this.largeResults) {
				pStmtLoad.setInt(1, 1);
			}

			this.rs = pStmtLoad.executeQuery();

			if (this.rs.next()) {
				int key = this.rs.getInt(1);

				if (!this.useColumnNames) {
					pStmtStore.setInt(1, key); // priKey
					pStmtStore.setInt(2, this.rs.getInt(2)); // fk1
					pStmtStore.setInt(3, this.rs.getInt(3)); // fk2
					pStmtStore.setTimestamp(4, this.rs.getTimestamp(4)); // dtField
					pStmtStore.setString(5, this.rs.getString(5)); // charField1
					pStmtStore.setString(6, this.rs.getString(7)); // charField2
					pStmtStore.setString(7, this.rs.getString(7)); // charField3
					pStmtStore.setString(8, this.rs.getString(8)); // charField4
					pStmtStore.setInt(9, this.rs.getInt(9)); // intField1
					pStmtStore.setInt(10, this.rs.getInt(10)); // intField2
					pStmtStore.setInt(11, this.rs.getInt(11)); // intField3
					pStmtStore.setInt(12, this.rs.getInt(12)); // intField4
					pStmtStore.setDouble(13, this.rs.getDouble(13)); // doubleField1
					pStmtStore.setDouble(14, this.rs.getDouble(14)); // doubleField2
					pStmtStore.setDouble(15, this.rs.getDouble(15)); // doubleField3
					pStmtStore.setDouble(16, this.rs.getDouble(16)); // doubleField4

					pStmtStore.setInt(17, key);
				} else {
					/*
					 * "UPDATE perfLoadStore SET " + "priKey = ?, " + "fk1 = ?, " +
					 * "fk2 = ?, " + "dtField = ?, " + "charField1 = ?, " +
					 * "charField2 = ?, " + "charField3 = ?, " + "charField4 = ?, " +
					 * "intField1 = ?, " + "intField2 = ?, " + "intField3 = ?, " +
					 * "intField4 = ?, " + "doubleField1 = ?," + "doubleField2 =
					 * ?," + "doubleField3 = ?," + "doubleField4 = ?" + " WHERE
					 * priKey=?");
					 */
					pStmtStore.setInt(1, key); // priKey
					pStmtStore.setInt(2, this.rs.getInt("fk1")); // fk1
					pStmtStore.setInt(3, this.rs.getInt("fk2")); // fk2
					pStmtStore.setTimestamp(4, this.rs.getTimestamp("dtField")); // dtField
					pStmtStore.setString(5, this.rs.getString("charField1")); // charField1
					pStmtStore.setString(6, this.rs.getString("charField2")); // charField2
					pStmtStore.setString(7, this.rs.getString("charField3")); // charField3
					pStmtStore.setString(8, this.rs.getString("charField4")); // charField4
					pStmtStore.setInt(9, this.rs.getInt("intField1")); // intField1
					pStmtStore.setInt(10, this.rs.getInt("intField2")); // intField2
					pStmtStore.setInt(11, this.rs.getInt("intField3")); // intField3
					pStmtStore.setInt(12, this.rs.getInt("intField4")); // intField4
					pStmtStore.setDouble(13, this.rs.getDouble("doubleField1")); // doubleField1
					pStmtStore.setDouble(14, this.rs.getDouble("doubleField2")); // doubleField2
					pStmtStore.setDouble(15, this.rs.getDouble("doubleField3")); // doubleField3
					pStmtStore.setDouble(16, this.rs.getDouble("doubleField4")); // doubleField4

					pStmtStore.setInt(17, key);
				}

				pStmtStore.executeUpdate();
			}

			this.rs.close();

			this.conn.commit();
			this.conn.setAutoCommit(true);
		}

		pStmtStore.close();
		pStmtCheck.close();
		pStmtLoad.close();

		long end = System.currentTimeMillis();

		long timeElapsed = (end - begin);

		double timeElapsedSeconds = (double) timeElapsed / 1000;
		double tps = transactionCount / timeElapsedSeconds;

		if (this.takeMeasurements) {
			addResult(tps);
			System.out.print("1 [ " + numFormatter.format(getMeanValue())
					+ " ] ");
		} else {
			System.out.println("Warm-up: " + tps + " trans/sec");
		}
	}

	/**
	 * Runs the test 10 times to get JIT going, and GC going
	 * 
	 * @throws Exception
	 *             if an error occurs.
	 */
	protected void warmUp() throws Exception {
		try {
			System.out.print("Warm-up period (10 iterations)");

			for (int i = 0; i < 10; i++) {
				doOneIteration();
				System.out.print(".");
			}

			System.out.println();
			System.out.println("Warm-up period ends");
			System.out.println("\nUnits for this test are transactions/sec.");
		} catch (Exception ex) {
			ex.printStackTrace();

			throw ex;
		}
	}
}
