/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltdb.regressionsuites; import java.io.IOException; import java.util.Random; import org.voltdb.BackendTarget; import org.voltdb.VoltTable; import org.voltdb.VoltType; import org.voltdb.client.Client; import org.voltdb.client.ClientResponse; import org.voltdb.client.NullCallback; import org.voltdb.client.ProcCallException; import org.voltdb.compiler.VoltProjectBuilder; import org.voltdb.utils.MiscUtils; import org.voltdb_testprocs.regressionsuites.malicious.GoSleep; import junit.framework.Test; public class TestSystemProcedureSuite extends RegressionSuite { private static int SITES = 3; private static int HOSTS = MiscUtils.isPro() ? 2 : 1; private static int KFACTOR = MiscUtils.isPro() ? 1 : 0; private static boolean hasLocalServer = true; //// false; // Having 2 different table name prefixes simplifies the process of // writing and maintaining positive @SwapTables test cases which require // pairs of identical (or nearly so) tables. static final String[] SWAPPY_PREFIX_PAIR = { "SWAP_THIS", "SWAP_THAT" }; // Entries in this array are three element arrays defining customizations // to a base table, each having a different name ending: e.g. // "_NORMAL" for a base case with no customizations. // The first element of each three element array is the root/suffix part // of the table name. // The second element is a continuation of the common table definition line // that starts "ID INTEGER " and can optionally be extended with additional // syntax to define column qualifiers for ID, and/or a comma to end the // column declaration followed by a constraint definition. // The third element is one or more table modifier statements to establish // partitioning and/or external indexes for that variant of the table // after the table definition. private static final String[][] SWAPPY_TABLES = { { "_NORMAL", // 0 "", "", }, { "_WITH_PARTITIONING", // 1 " NOT NULL", " PARTITION TABLE %s_WITH_PARTITIONING ON COLUMN ID;", }, { "_WITH_NAMED_TREE_PK", // 2 ", CONSTRAINT %s_NAMED PRIMARY KEY (ID)\n", "", }, { "_WITH_ANONYMOUS_PK", // 3 ", PRIMARY KEY (ID)\n", "", }, { "_WITH_INLINE_PK", // 4 " PRIMARY KEY NOT NULL\n", "", }, { "_WITH_NAMED_UNIQUE", // 5 ", CONSTRAINT %s_NAMED UNIQUE (ID)\n", "", }, { "_WITH_ANONYMOUS_UNIQUE", // 6 ", UNIQUE (ID)\n", "", }, { "_WITH_INLINE_UNIQUE", // 7 " UNIQUE NOT NULL\n", "", }, { "_WITH_EXTERNAL_UNIQUE", // 8 "", " CREATE UNIQUE INDEX %s_WITH_EXTERNAL_UNIQUE_INDEX ON %s_WITH_EXTERNAL_UNIQUE (ID);\n", }, { "_WITH_NAMED_ASSUME_UNIQUE", // 9 ", CONSTRAINT %s_NAMED ASSUMEUNIQUE (ID)\n", " PARTITION TABLE %s_WITH_NAMED_ASSUME_UNIQUE ON COLUMN NAME;\n", }, { "_WITH_ANONYMOUS_ASSUME_UNIQUE", // 10 ", ASSUMEUNIQUE (ID)\n", " PARTITION TABLE %s_WITH_ANONYMOUS_ASSUME_UNIQUE ON COLUMN NAME;\n", }, { "_WITH_INLINE_ASSUME_UNIQUE", // 11 " ASSUMEUNIQUE NOT NULL\n", " PARTITION TABLE %s_WITH_INLINE_ASSUME_UNIQUE ON COLUMN NAME;\n", }, { "_WITH_EXTERNAL_ASSUME_UNIQUE", // 12 "", " PARTITION TABLE %s_WITH_EXTERNAL_ASSUME_UNIQUE ON COLUMN NAME;\n" + " CREATE ASSUMEUNIQUE INDEX " + " %s_WITH_EXTERNAL_ASSUME_UNIQUE_ID " + " ON %s_WITH_EXTERNAL_ASSUME_UNIQUE (ID);\n", }, { "_WITH_EXTERNAL_NONUNIQUE", // 13 "", " CREATE INDEX %s_WITH_EXTERNAL_NONUNIQUE ON %s_WITH_EXTERNAL_NONUNIQUE (ID);\n", }, { "_WITH_ANONYMOUS_ALTPK", // 14 ", PRIMARY KEY (NONID)\n", "", }, { "_WITH_NAMED_LIMIT_ROWS_100", // 15 ", CONSTRAINT %s_NAMED LIMIT PARTITION ROWS 100\n", "", }, { "_WITH_ANONYMOUS_LIMIT_ROWS_100", // 16 ", LIMIT PARTITION ROWS 100\n", "", }, { "_WITH_NAMED_LIMIT_ROWS_1000", // 17 ", CONSTRAINT %s_NAMED LIMIT PARTITION ROWS 1000\n", "", }, { "_WITH_ANONYMOUS_LIMIT_ROWS_1000", // 18 ", LIMIT PARTITION ROWS 1000\n", "", }, { "_WITH_PARTIAL_INDEX", // 19 "", " CREATE INDEX %s_WITH_PARTIAL_INDEX ON %s_WITH_PARTIAL_INDEX (ID) WHERE ID > 0;\n", }, { "_WITH_ALT_PARTITIONING", // 1 "", " PARTITION TABLE %s_WITH_ALT_PARTITIONING ON COLUMN NONID;", }, { "_WITH_MANY_FEATURES", // 20 ", PRIMARY KEY (ID), UNIQUE (NONID, NAME), LIMIT PARTITION ROWS 1000", " CREATE INDEX %s_WITH_MANY_FEATURES ON %s_WITH_MANY_FEATURES (NONID / 2); " + "CREATE INDEX %s_PARTIAL_WITH_MANY_FEATURES ON %s_WITH_MANY_FEATURES (NONID / 3) WHERE NONID > 1000;", } }; // These template tables only need one instantiation each and are // more complete than the SWAPPY_TABLES templates for more flexibility. // Each entry is a two string vector. The first string is a complete table // name and the second is a complete body of the definition for the table. // The main motivation for breaking apart the table name and its // definition rather than just using a big block of literal schema text // is to allow iteration over just the table names when generating // the fixed set of DML and query statements against each of them. // // { "ORIG", // " (\n" + // " NAME VARCHAR(32 BYTES) NOT NULL," + // " PRICE FLOAT," + // " NONID INTEGER NOT NULL," + // " ID INTEGER " + // ");\n", // }, private static final String[][] NONSWAPPY_TABLES = { { "NONSWAP_REORDERED", " (\n" + " PRICE FLOAT," + " NAME VARCHAR(32 BYTES) NOT NULL," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_RENAMED", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL," + " APRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_RETYPED", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL," + " PRICE DECIMAL," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_RETYPED2", " (\n" + " NAME VARCHAR(33 BYTES) NOT NULL," + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_RETYPED3", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL," + " PRICE FLOAT," + " NONID BIGINT NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_NOT_BYTES", " (\n" + " NAME VARCHAR(32) NOT NULL," + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_NOT_BYTES2", " (\n" + " NAME VARCHAR(8) NOT NULL," + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_SHORTENED", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + ");\n", }, { "NONSWAP_INSERTED", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " LIST_PRICE FLOAT," + " ID INTEGER " + ");\n", }, { "NONSWAP_STRUNG_UP", " (\n" + " NAME VARCHAR(128 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n", }, { "NONSWAP_BIGGER", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID BIGINT " + ");\n", }, { "NONSWAP_VIEWED_1", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n" + "CREATE VIEW VIEWING_1 AS SELECT ID, COUNT(*), SUM(PRICE) \n" + " FROM NONSWAP_VIEWED_1 GROUP BY ID;\n" }, { "NONSWAP_VIEWED_2", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n" + "CREATE TABLE JOINED_2 (DISCOUNT FLOAT, ID INTEGER);\n" + "CREATE VIEW VIEWING_2 AS SELECT A.ID, COUNT(*), SUM(A.PRICE*B.DISCOUNT) \n" + "FROM NONSWAP_VIEWED_2 A, JOINED_2 B WHERE A.ID = B.ID GROUP BY A.ID;\n" }, { "NONSWAP_VIEWED_3", " (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER " + ");\n" + "CREATE TABLE JOINED_3 (DISCOUNT FLOAT, ID INTEGER);\n" + "CREATE VIEW VIEWING_3A AS SELECT ID, COUNT(*), SUM(PRICE) \n" + " FROM NONSWAP_VIEWED_3 GROUP BY ID;\n" + "CREATE VIEW VIEWING_3B AS SELECT A.ID, COUNT(*), SUM(A.PRICE*B.DISCOUNT) \n" + " FROM NONSWAP_VIEWED_3 A, JOINED_3 B WHERE A.ID = B.ID GROUP BY A.ID;\n", }, { "NONSWAP_NO_MATCHING_COLUMNS", " (\n" + " FOO FLOAT NOT NULL " + ");\n", }, }; public TestSystemProcedureSuite(String name) { super(name); } private static void addSwappyTables(String prefix, VoltProjectBuilder project) throws Exception { StringBuilder schema = new StringBuilder(); for (String[] tableDefPart : SWAPPY_TABLES) { String tableName = prefix + tableDefPart[0]; String internalExtras = String.format(tableDefPart[1], tableName); String externalExtras = String.format(tableDefPart[2], prefix, prefix, prefix, prefix, prefix); schema.append("CREATE TABLE ").append(tableName) .append(" (\n" + " NAME VARCHAR(32 BYTES) NOT NULL,\n" + " PRICE FLOAT," + " NONID INTEGER NOT NULL," + " ID INTEGER ").append(internalExtras) .append(");\n") .append(externalExtras); } //*enable to debug*/ System.out.println(schema.toString()); project.addLiteralSchema(schema.toString()); } // Non-swappy tables are all incompatible for swapping with SWAP_THIS_NORMAL // due to various column differences or from their defined views. private static void addNonSwappyTables(VoltProjectBuilder project) throws Exception { StringBuilder schema = new StringBuilder(); for (String[] tableDefPart : NONSWAPPY_TABLES) { schema.append("CREATE TABLE ") .append(tableDefPart[0]) .append(tableDefPart[1]); } //*enable to debug*/ System.out.println(schema.toString()); project.addLiteralSchema(schema.toString()); } public void testPing() throws IOException, ProcCallException { Client client = getClient(); ClientResponse cr = client.callProcedure("@Ping"); assertEquals(ClientResponse.SUCCESS, cr.getStatus()); } private void checkProSysprocError(Client client, String name, int paramCount) throws Exception { // make some dummy params... real ones aren't needed for this test Object[] params = new Object[paramCount]; for (int i = 0; i < paramCount; i++) { params[i] = i; } try { client.callProcedure(name, params); fail("ORLY " + name + " succeeded w/out pro enabled?"); } catch (ProcCallException ex) { ClientResponse response = ex.getClientResponse(); assertEquals(ClientResponse.GRACEFUL_FAILURE, response.getStatus()); String status = response.getStatusString(); if ( ! status.contains("Enterprise Edition")) { System.out.println("sup w/ this status string: " + status); } assertTrue(status.contains("Enterprise")); } } public void testProSysprocErrorOnCommunity() throws Exception { // this test only applies to community edition if (MiscUtils.isPro()) { return; } Client client = getClient(); checkProSysprocError(client, "@SnapshotSave", 3); checkProSysprocError(client, "@SnapshotRestore", 2); checkProSysprocError(client, "@SnapshotStatus", 0); checkProSysprocError(client, "@SnapshotScan", 2); checkProSysprocError(client, "@SnapshotDelete", 2); // Turns out we don't flag @Promote as enterprise. Not touching that right now. --izzy //checkProSysprocError(client, "@Promote", 0); } public void testInvalidProcedureName() throws IOException { Client client = getClient(); try { client.callProcedure("@SomeInvalidSysProcName", "1", "2"); } catch (Exception e2) { assertEquals("Procedure @SomeInvalidSysProcName was not found", e2.getMessage()); return; } fail("Expected exception."); } private final String m_loggingConfig = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + "<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">" + "<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\">" + "<appender name=\"Console\" class=\"org.apache.log4j.ConsoleAppender\">" + "<param name=\"Target\" value=\"System.out\" />" + "<layout class=\"org.apache.log4j.TTCCLayout\">" + "</layout>" + "</appender>" + "<appender name=\"Async\" class=\"org.apache.log4j.AsyncAppender\">" + "<param name=\"Blocking\" value=\"true\" />" + "<appender-ref ref=\"Console\" /> " + "</appender>" + "<root>" + "<priority value=\"info\" />" + "<appender-ref ref=\"Async\" />" + "</root>" + "</log4j:configuration>"; public void testUpdateLogging() throws Exception { Client client = getClient(); VoltTable results[] = null; results = client.callProcedure("@UpdateLogging", m_loggingConfig).getResults(); for (VoltTable result : results) { assertEquals( 0, result.asScalarLong()); } } public void testPromoteMaster() throws Exception { Client client = getClient(); try { client.callProcedure("@Promote"); fail("ORLY @Promote succeeded?"); } catch (ProcCallException pce) { assertEquals(ClientResponse.GRACEFUL_FAILURE, pce.getClientResponse().getStatus()); } } // Pretty lame test but at least invoke the procedure. // "@Quiesce" is used more meaningfully in TestExportSuite. public void testQuiesce() throws IOException, ProcCallException { Client client = getClient(); VoltTable results[] = client.callProcedure("@Quiesce").getResults(); assertEquals(1, results.length); results[0].advanceRow(); assertEquals(results[0].get(0, VoltType.BIGINT), new Long(0)); } public void testLoadMultipartitionTableProceduresUpsertWithNoPrimaryKey() throws Exception{ // using insert for @Load*Table byte upsertMode = (byte) 1; Client client = getClient(); // should not be able to upsert to new_order since it has no primary key try { client.callProcedure("@LoadMultipartitionTable", "new_order", upsertMode, null); fail("ORLY @LoadMultipartitionTable new_order succeeded w/o a primary key?"); } catch (ProcCallException ex) { assertTrue(ex.getMessage().contains("the table new_order does not have a primary key")); } } public void testLoadMultipartitionTableAndIndexStatsAndValidatePartitioning() throws Exception { // using insert for @Load*Table byte upsertMode = (byte) 0; Client client = getClient(); // Load a little partitioned data for the mispartitioned row check Random r = new Random(0); for (int ii = 0; ii < 50; ii++) { client.callProcedure(new NullCallback(), "@AdHoc", "INSERT INTO new_order values (" + (short)(r.nextDouble() * Short.MAX_VALUE) + ");"); } // try the failure case first try { client.callProcedure("@LoadMultipartitionTable", "DOES_NOT_EXIST", upsertMode, null); fail("ORLY - @LoadMultipartitionTable DOES_NOT_EXIST succeeds"); } catch (ProcCallException ex) { assertTrue(ex.getMessage().contains("Table not present in catalog")); } // make a TPCC warehouse table VoltTable partitioned_table = new VoltTable( new VoltTable.ColumnInfo("W_ID", org.voltdb.VoltType.SMALLINT), new VoltTable.ColumnInfo("W_NAME", org.voltdb.VoltType.get((byte)9)), new VoltTable.ColumnInfo("W_STREET_1", org.voltdb.VoltType.get((byte)9)), new VoltTable.ColumnInfo("W_STREET_2", org.voltdb.VoltType.get((byte)9)), new VoltTable.ColumnInfo("W_CITY", org.voltdb.VoltType.get((byte)9)), new VoltTable.ColumnInfo("W_STATE", org.voltdb.VoltType.get((byte)9)), new VoltTable.ColumnInfo("W_ZIP", org.voltdb.VoltType.get((byte)9)), new VoltTable.ColumnInfo("W_TAX",org.voltdb.VoltType.get((byte)8)), new VoltTable.ColumnInfo("W_YTD", org.voltdb.VoltType.get((byte)8)) ); for (int i = 1; i < 21; i++) { Object[] row = new Object[] {new Short((short) i), "name_" + i, "street1_" + i, "street2_" + i, "city_" + i, "ma", "zip_" + i, new Double(i), new Double(i)}; partitioned_table.addRow(row); } // make a TPCC item table VoltTable replicated_table = new VoltTable(new VoltTable.ColumnInfo("I_ID", VoltType.INTEGER), new VoltTable.ColumnInfo("I_IM_ID", VoltType.INTEGER), new VoltTable.ColumnInfo("I_NAME", VoltType.STRING), new VoltTable.ColumnInfo("I_PRICE", VoltType.FLOAT), new VoltTable.ColumnInfo("I_DATA", VoltType.STRING)); for (int i = 1; i < 21; i++) { Object[] row = new Object[] {i, i, "name_" + i, new Double(i), "data_" + i}; replicated_table.addRow(row); } try { try { client.callProcedure("@LoadMultipartitionTable", "WAREHOUSE", upsertMode, partitioned_table); fail("ORLY - @LoadMultipartitionTable succeeds on partitioned WAREHOUSE table?"); } catch (ProcCallException ex) { assertTrue(ex.getMessage().contains( "LoadMultipartitionTable no longer supports loading partitioned tables")); } client.callProcedure("@LoadMultipartitionTable", "ITEM", upsertMode, replicated_table); // 20 rows per site for the replicated table. Wait for it... int rowcount = 0; VoltTable results[] = client.callProcedure("@Statistics", "table", 0).getResults(); while (rowcount != (20 * SITES * HOSTS)) { rowcount = 0; results = client.callProcedure("@Statistics", "table", 0).getResults(); // Check that tables loaded correctly while(results[0].advanceRow()) { if (results[0].getString("TABLE_NAME").equals("ITEM")) { rowcount += results[0].getLong("TUPLE_COUNT"); } } } //*enable to debug*/ System.out.println(results[0]); // Check that tables loaded correctly int foundItem = 0; results = client.callProcedure("@Statistics", "table", 0).getResults(); while(results[0].advanceRow()) { if (results[0].getString("TABLE_NAME").equals("ITEM")) { ++foundItem; //Different values depending on local cluster vs. single process hence || assertEquals(20, results[0].getLong("TUPLE_COUNT")); } } assertEquals(MiscUtils.isPro() ? 6 : 3, foundItem); // Table finally loaded fully should mean that index is okay on first read. VoltTable indexStats = client.callProcedure("@Statistics", "INDEX", 0).getResults()[0]; //*enable to debug*/ System.out.println(indexStats); long memorySum = 0; while (indexStats.advanceRow()) { memorySum += indexStats.getLong("MEMORY_ESTIMATE"); } // // It takes about a minute to spin through this 1000 times. // Should definitely give a 1 second tick time to fire // long indexMemorySum = 0; for (int ii = 0; ii < 1000; ii++) { indexMemorySum = 0; indexStats = client.callProcedure("@Statistics", "MEMORY", 0).getResults()[0]; //*enable to debug*/ System.out.println(indexStats); while (indexStats.advanceRow()) { indexMemorySum += indexStats.getLong("INDEXMEMORY"); } boolean success = indexMemorySum != 120;//That is a row count, not memory usage if (success) { success = memorySum == indexMemorySum; if (success) { break; } } Thread.sleep(1); } assertTrue(indexMemorySum != 120);//That is a row count, not memory usage assertEquals(memorySum, indexMemorySum); // // Test once using the current correct hash function, // expect no mispartitioned rows // ClientResponse cr = client.callProcedure("@ValidatePartitioning", 0, null); VoltTable hashinatorMatches = cr.getResults()[1]; while (hashinatorMatches.advanceRow()) { assertEquals(1L, hashinatorMatches.getLong("HASHINATOR_MATCHES")); } VoltTable validateResult = cr.getResults()[0]; //*enable to debug*/ System.out.println(validateResult); while (validateResult.advanceRow()) { assertEquals(0L, validateResult.getLong("MISPARTITIONED_ROWS")); } // // Test again with a bad hash function, expect mispartitioned rows // cr = client.callProcedure("@ValidatePartitioning", 0, new byte[] { 0, 0, 0, 9 }); hashinatorMatches = cr.getResults()[1]; while (hashinatorMatches.advanceRow()) { assertEquals(0L, hashinatorMatches.getLong("HASHINATOR_MATCHES")); } validateResult = cr.getResults()[0]; //*enable to debug*/ System.out.println(validateResult); while (validateResult.advanceRow()) { if (validateResult.getString("TABLE").equals("NEW_ORDER")) { assertTrue(validateResult.getLong("MISPARTITIONED_ROWS") > 0); } } } catch (Exception ex) { ex.printStackTrace(); fail(); } } // verify that these commands don't blow up public void testProfCtl() throws Exception { Client client = getClient(); // // SAMPLER_START // ClientResponse resp = client.callProcedure("@ProfCtl", "SAMPLER_START"); VoltTable vt = resp.getResults()[0]; boolean foundResponse = false; while (vt.advanceRow()) { String profCtlResult = vt.getString("Result"); if ("SAMPLER_START".equalsIgnoreCase(profCtlResult)) { foundResponse = true; } else { fail("Was not expecting @ProfCtl result: " + profCtlResult); } } assertTrue(foundResponse); // // GPERF_ENABLE // resp = client.callProcedure("@ProfCtl", "GPERF_ENABLE"); vt = resp.getResults()[0]; foundResponse = false; while (vt.advanceRow()) { String profCtlResult = vt.getString("Result"); if ("GPERF_ENABLE".equalsIgnoreCase(profCtlResult)) { foundResponse = true; } else { assertTrue("GPERF_NOOP".equalsIgnoreCase(profCtlResult)); } } assertTrue(foundResponse); // // GPERF_DISABLE // resp = client.callProcedure("@ProfCtl", "GPERF_DISABLE"); vt = resp.getResults()[0]; foundResponse = false; while (vt.advanceRow()) { String profCtlResult = vt.getString("Result"); if ("GPERF_DISABLE".equalsIgnoreCase(profCtlResult)) { foundResponse = true; } else { assertTrue("GPERF_NOOP".equalsIgnoreCase(profCtlResult)); } } assertTrue(foundResponse); // // garbage // resp = client.callProcedure("@ProfCtl", "MakeAPony"); vt = resp.getResults()[0]; } public void testPause() throws Exception { Client client = getClient(); VoltTable[] results = client.callProcedure("@UpdateLogging", m_loggingConfig).getResults(); for (VoltTable result : results) { assertEquals(0, result.asScalarLong()); } ClientResponse resp = client.callProcedure("pauseTestInsert"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); client.callProcedure("@AdHoc", "INSERT INTO pause_test_tbl values (10);"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); // pause Client admin = getAdminClient(); resp = admin.callProcedure("@Pause"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); try { client.callProcedure("@AdHoc", "INSERT INTO pause_test_tbl values (20);"); fail("AdHoc insert did not fail in pause mode"); } catch (ProcCallException ex) { assertEquals(ClientResponse.SERVER_UNAVAILABLE, ex.getClientResponse().getStatus()); } try { client.callProcedure("@AdHoc", "CREATE TABLE ddl_test1 (fld1 integer NOT NULL);"); fail("AdHoc create did not fail in pause mode"); } catch (ProcCallException ex) { assertTrue(ex.getMessage().contains("Server is paused")); assertEquals(ClientResponse.SERVER_UNAVAILABLE, ex.getClientResponse().getStatus()); } try { client.callProcedure("@AdHoc", "DROP TABLE pause_test_tbl;"); fail("AdHoc drop did not fail in pause mode"); } catch (ProcCallException ex) { assertTrue(ex.getMessage().contains("Server is paused")); assertEquals(ClientResponse.SERVER_UNAVAILABLE, ex.getClientResponse().getStatus()); } try { client.callProcedure("@AdHoc", "CREATE PROCEDURE pause_test_proc AS SELECT * FROM pause_test_tbl;"); fail("AdHoc create proc did not fail in pause mode"); } catch (ProcCallException ex) { assertTrue(ex.getMessage().contains("Server is paused")); assertEquals(ClientResponse.SERVER_UNAVAILABLE, ex.getClientResponse().getStatus()); } // admin should work fine admin.callProcedure("@AdHoc", "INSERT INTO pause_test_tbl values (20);"); admin.callProcedure("@AdHoc", "CREATE TABLE ddl_test1 (fld1 integer NOT NULL);"); admin.callProcedure("@AdHoc", "CREATE PROCEDURE pause_test_proc AS SELECT * FROM pause_test_tbl;"); admin.callProcedure("@AdHoc", "DROP TABLE ddl_test1;"); admin.callProcedure("@AdHoc", "DROP PROCEDURE pause_test_proc;"); try { resp = client.callProcedure("@UpdateLogging", m_loggingConfig); fail(); } catch (ProcCallException ex) { assertEquals(ClientResponse.SERVER_UNAVAILABLE, ex.getClientResponse().getStatus()); } try { resp = client.callProcedure("pauseTestInsert"); fail(); } catch (ProcCallException ex) { assertEquals(ClientResponse.SERVER_UNAVAILABLE, ex.getClientResponse().getStatus()); } resp = client.callProcedure("@Ping"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); resp = client.callProcedure("@AdHoc", "SELECT COUNT(*) FROM pause_test_tbl"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); assertEquals(3, resp.getResults()[0].asScalarLong()); resp = client.callProcedure("pauseTestCount"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); assertEquals(3, resp.getResults()[0].asScalarLong()); // resume resp = admin.callProcedure("@Resume"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); results = client.callProcedure("@UpdateLogging", m_loggingConfig).getResults(); for (VoltTable result : results) { assertEquals(0, result.asScalarLong()); } } private final static Object[][] THE_SWAP_CONTENTS = {{"1", 1.0, 1, 1}}; private static Object[][] OTHER_SWAP_CONTENTS = {{"2", null, 2, 2}, {"3", 3.0, 3, 3}}; private void populateSwappyTables(Client client, String thisTable, String thatTable) throws Exception { for (Object[] row : THE_SWAP_CONTENTS) { client.callProcedure(thisTable + ".insert", row); } for (Object[] row : OTHER_SWAP_CONTENTS) { client.callProcedure(thatTable + ".insert", row); } } public void testSwapTables() throws Exception { Client client = getClient(); for (int ii = 0; ii < SWAPPY_TABLES.length; ++ii) { String theTable = SWAPPY_PREFIX_PAIR[0] + SWAPPY_TABLES[ii][0]; for (int jj = 0; jj < SWAPPY_TABLES.length; ++jj) { // Allow swapping only for identically defined tables // or those with minor variations in syntax. String otherTable = SWAPPY_PREFIX_PAIR[1] + SWAPPY_TABLES[jj][0]; //*enable to debug*/ System.out.println("ii = " + ii + ", jj = " +jj); //*enable to debug*/ System.out.println("@SwapTables " + theTable + " " + otherTable); // These pairs of numbers are indexes into SWAPPY_TABLES that // represent definitions that are "close enough" for swapping // purposes. These were experimentally discovered and then // verified for reasonableness. if ((ii == jj) || (ii == 2 && jj == 3) || (ii == 3 && jj == 2) || (ii == 2 && jj == 4) || (ii == 4 && jj == 2) || (ii == 3 && jj == 4) || (ii == 4 && jj == 3) || (ii == 5 && jj == 6) || (ii == 6 && jj == 5) || (ii == 5 && jj == 7) || (ii == 7 && jj == 5) || (ii == 6 && jj == 7) || (ii == 7 && jj == 6) || (ii == 9 && jj == 10) || (ii == 10 && jj == 9) || (ii == 9 && jj == 11) || (ii == 11 && jj == 9) || (ii == 10 && jj == 11) || (ii == 11 && jj == 10) || (ii == 15 && jj == 16) || (ii == 16 && jj == 15) || (ii == 17 && jj == 18) || (ii == 18 && jj == 17)) { VoltTable[] results; populateSwappyTables(client, theTable, otherTable); results = client.callProcedure("@SwapTables", theTable, otherTable).getResults(); //*enable to debug*/ System.out.println(results[0]); assertNotNull(results); assertEquals(1, results.length); assertEquals(3, results[0].asScalarLong()); VoltTable contents = client.callProcedure("@AdHoc", "select * from " + theTable + " order by id").getResults()[0]; assertContentOfTable(OTHER_SWAP_CONTENTS, contents); contents = client.callProcedure("@AdHoc", "select * from " + otherTable + " order by id").getResults()[0]; assertContentOfTable(THE_SWAP_CONTENTS, contents); // Swap again to restore the baseline populations. results = client.callProcedure("@SwapTables", otherTable, theTable).getResults(); //*enable to debug*/ System.out.println(results[0]); assertNotNull(results); assertEquals(1, results.length); assertEquals(3, results[0].asScalarLong()); // Verify that baseline is restored contents = client.callProcedure("@AdHoc", "select * from " + theTable + " order by id").getResults()[0]; assertContentOfTable(THE_SWAP_CONTENTS, contents); contents = client.callProcedure("@AdHoc", "select * from " + otherTable + " order by id").getResults()[0]; assertContentOfTable(OTHER_SWAP_CONTENTS, contents); results = client.callProcedure("@AdHoc", "TRUNCATE TABLE " + theTable).getResults(); assertEquals(1, results[0].asScalarLong()); // Try a swap with one empty table. results = client.callProcedure("@SwapTables", otherTable, theTable).getResults(); assertNotNull(results); assertEquals(1, results.length); assertEquals(2, results[0].asScalarLong()); contents = client.callProcedure("@AdHoc", "select * from " + theTable + " order by id").getResults()[0]; assertContentOfTable(OTHER_SWAP_CONTENTS, contents); contents = client.callProcedure("@AdHoc", "select * from " + otherTable + " order by id").getResults()[0]; assertContentOfTable(new Object[][] {}, contents); results = client.callProcedure("@AdHoc", "TRUNCATE TABLE " + theTable).getResults(); assertEquals(2, results[0].asScalarLong()); contents = client.callProcedure("@AdHoc", "select * from " + theTable + " order by id").getResults()[0]; assertContentOfTable(new Object[][] {}, contents); contents = client.callProcedure("@AdHoc", "select * from " + otherTable + " order by id").getResults()[0]; assertContentOfTable(new Object[][] {}, contents); // Try a swap with both empty tables. results = client.callProcedure("@SwapTables", otherTable, theTable).getResults(); assertNotNull(results); assertEquals(1, results.length); assertEquals(0, results[0].asScalarLong()); continue; } try { client.callProcedure("@SwapTables", theTable, otherTable); fail("Swap should have conflicted on table definitions " + ii + " and " + jj + " : " + theTable + " with " + otherTable); } catch (ProcCallException ex) { if (! ex.getMessage().contains("Swapping")) { System.out.println("sup w/ incompatible tables " + ii + " and " + jj + ":" + ex); } assertTrue("Expected message about swapping but got " + ex.getMessage(), ex.getMessage().contains("Swapping")); } } } } public void testOneOffNegativeSwapTables() throws Exception { Client client = getClient(); String tableA = SWAPPY_PREFIX_PAIR[0] + SWAPPY_TABLES[0][0]; for (int ii = 0; ii < NONSWAPPY_TABLES.length; ++ii) { String tableB = NONSWAPPY_TABLES[ii][0]; try { client.callProcedure("@SwapTables", tableA, tableB); fail("Swap should have conflicted on table definitions " + tableA + " and " + tableB); } catch (ProcCallException ex) { if (! ex.getMessage().contains("Swapping")) { System.out.println("sup w/ these incompatible tables(" + tableA + " and " + tableB + "): " + ex); } assertTrue(ex.getMessage().contains("Swapping")); } // Try reversing the arguments. Incompatibility should be mutual. try { client.callProcedure("@SwapTables", tableB, tableA); fail("Swap should have conflicted on table definitions " + tableB + " and " + tableA); } catch (ProcCallException ex) { if (! ex.getMessage().contains("Swapping")) { System.out.println("sup w/ these incompatible tables(" + tableB + " and " + tableA + "): " + ex); } assertTrue(ex.getMessage().contains("Swapping")); } } } // // Build a list of the tests to be run. Use the regression suite // helpers to allow multiple backends. // JUnit magic that uses the regression suite helper classes. // static public Test suite() throws Exception { // Not really using TPCC functionality but need a schema. // The testLoadMultipartitionTable procedure assumes partitioning // on warehouse id. VoltProjectBuilder project = new VoltProjectBuilder(); String literalSchema = "CREATE TABLE WAREHOUSE (\n" + " W_ID SMALLINT DEFAULT 0 NOT NULL,\n" + " W_NAME VARCHAR(16),\n" + " W_STREET_1 VARCHAR(32),\n" + " W_STREET_2 VARCHAR(32),\n" + " W_CITY VARCHAR(32),\n" + " W_STATE VARCHAR(2),\n" + " W_ZIP VARCHAR(9),\n" + " W_TAX FLOAT,\n" + " W_YTD FLOAT,\n" + " CONSTRAINT W_PK_TREE PRIMARY KEY (W_ID)\n" + ");\n" + "CREATE TABLE ITEM (\n" + " I_ID INTEGER DEFAULT 0 NOT NULL,\n" + " I_IM_ID INTEGER,\n" + " I_NAME VARCHAR(32),\n" + " I_PRICE FLOAT,\n" + " I_DATA VARCHAR(64),\n" + " CONSTRAINT I_PK_TREE PRIMARY KEY (I_ID)\n" + ");\n" + "CREATE TABLE NEW_ORDER (\n" + " NO_W_ID SMALLINT DEFAULT 0 NOT NULL\n" + ");\n" + "CREATE TABLE PAUSE_TEST_TBL (\n" + " TEST_ID SMALLINT DEFAULT 0 NOT NULL\n" + ");\n" + ""; project.addLiteralSchema(literalSchema); // testSwapTables needs lots of variations on the same table, // including duplicates, to test compatibility criteria. for (String prefix : SWAPPY_PREFIX_PAIR) { addSwappyTables(prefix, project); } addNonSwappyTables(project); project.setUseDDLSchema(true); project.addPartitionInfo("WAREHOUSE", "W_ID"); project.addPartitionInfo("NEW_ORDER", "NO_W_ID"); project.addProcedures(GoSleep.class); project.addStmtProcedure("pauseTestCount", "SELECT COUNT(*) FROM pause_test_tbl"); project.addStmtProcedure("pauseTestInsert", "INSERT INTO pause_test_tbl VALUES (1)"); MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestSystemProcedureSuite.class); LocalCluster config; config = new LocalCluster("sysproc-cluster.jar", SITES, HOSTS, KFACTOR, BackendTarget.NATIVE_EE_JNI); if ( ! hasLocalServer ) { config.setHasLocalServer(false); } // boolean success = config.compile(project); boolean success = config.compileWithAdminMode(project, -1, false); assertTrue(success); builder.addServerConfig(config); return builder; } }