com.tesora.dve.sql.SQLVariableTest.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.sql.SQLVariableTest.java

Source

package com.tesora.dve.sql;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import com.tesora.dve.variables.VariableService;
import org.apache.commons.lang.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.tesora.dve.common.DBHelper;
import com.tesora.dve.common.PEStringUtils;
import com.tesora.dve.errmap.InternalErrors;
import com.tesora.dve.errmap.MySQLErrors;
import com.tesora.dve.exceptions.PECodingException;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.resultset.ColumnSet;
import com.tesora.dve.resultset.ResultColumn;
import com.tesora.dve.resultset.ResultRow;
import com.tesora.dve.server.bootstrap.BootstrapHost;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.parser.TimestampVariableUtils;
import com.tesora.dve.sql.schema.VariableScopeKind;
import com.tesora.dve.sql.util.ComparisonOptions;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.PEDDL;
import com.tesora.dve.sql.util.ProjectDDL;
import com.tesora.dve.sql.util.ProxyConnectionResource;
import com.tesora.dve.sql.util.ProxyConnectionResourceResponse;
import com.tesora.dve.sql.util.ResourceResponse;
import com.tesora.dve.sql.util.StorageGroupDDL;
import com.tesora.dve.standalone.PETest;
import com.tesora.dve.variables.VariableHandler;
import com.tesora.dve.variables.VariableManager;

public class SQLVariableTest extends SchemaTest {

    private static final ProjectDDL sysDDL = new PEDDL("sysdb", new StorageGroupDDL("sys", 2, "sysg"), "schema");

    private static final String nonRootUser = "ben";
    private static final String nonRootAccess = "localhost";

    @BeforeClass
    public static void setup() throws Throwable {
        PETest.projectSetup(sysDDL);
        PETest.bootHost = BootstrapHost.startServices(PETest.class);
        ProxyConnectionResource pcr = new ProxyConnectionResource();
        sysDDL.create(pcr);
        removeUser(pcr, nonRootUser, nonRootAccess);
        pcr.execute(
                String.format("create user '%s'@'%s' identified by '%s'", nonRootUser, nonRootAccess, nonRootUser));
        pcr.disconnect();
    }

    ProxyConnectionResource conn = null;

    @Before
    public void before() throws Throwable {
        conn = new ProxyConnectionResource();
    }

    @After
    public void after() throws Throwable {
        conn.disconnect();
    }

    @Test
    public void testA() throws Throwable {
        conn.execute("set names latin1");
        conn.assertResults("show session variables like 'character%'",
                br(nr, "character_set_client", "latin1", nr, "character_set_connection", "latin1", nr,
                        "character_set_database", "utf8", nr, "character_set_results", "latin1", nr,
                        "character_set_server", "utf8", nr, "character_set_system", "utf8"));
        conn.assertResults("show variables like 'character%'",
                br(nr, "character_set_client", "latin1", nr, "character_set_connection", "latin1", nr,
                        "character_set_database", "utf8", nr, "character_set_results", "latin1", nr,
                        "character_set_server", "utf8", nr, "character_set_system", "utf8"));
        conn.execute("set @prevcharset = @@character_set_connection");
        conn.execute("set names utf8");
        conn.assertResults("show session variables like 'character%'",
                br(nr, "character_set_client", "utf8", nr, "character_set_connection", "utf8", nr,
                        "character_set_database", "utf8", nr, "character_set_results", "utf8", nr,
                        "character_set_server", "utf8", nr, "character_set_system", "utf8"));
        conn.assertResults(
                "select @@character_set_client,@@session.character_set_connection,@@character_set_results",
                br(nr, "utf8", "utf8", "utf8"));
        conn.execute("set @@character_set_connection = @prevcharset");
        conn.assertResults("select @@character_set_connection", br(nr, "latin1"));
        conn.assertResults("show dve variables like 'version'",
                br(nr, "version", Singletons.require(HostService.class).getDveServerVersion()));
        try {
            // this should fail
            conn.execute("set version = '1.0'");
            fail("SET VERSION command should have thrown an exception");
        } catch (PEException re) {
            assertException(re, PEException.class, "Variable 'version' not settable as session variable");
        }
    }

    @Test
    public void testB() throws Throwable {
        conn.execute("use " + sysDDL.getDatabaseName());
        conn.execute("create table `vtest` (`id` int, `pl` varchar(32))");
        conn.execute("set @default_payload = 'this is some text'");
        conn.execute("set @default_id = 15");
        conn.assertResults("show variables like 'tx_isolation'", br(nr, "tx_isolation", "READ-COMMITTED"));
        conn.assertResults("select @@tx_isolation", br(nr, "READ-COMMITTED"));
        conn.execute("set transaction isolation level serializable");
        conn.assertResults("show variables like 'tx_isolation'", br(nr, "tx_isolation", "SERIALIZABLE"));
        conn.assertResults("select @@tx_isolation", br(nr, "SERIALIZABLE"));
        conn.execute(
                "insert into `vtest` (`id`, `pl`) values (1,'one'),(2,@default_payload),(@default_id, 'three')");
        conn.assertResults("select * from vtest where id = 15", br(nr, new Integer(15), "three"));
        conn.assertResults("select id from vtest where pl = @default_payload", br(nr, new Integer(2)));
    }

    @Test
    public void testShowVariables() throws Throwable {
        ResourceResponse rr = conn.fetch("show variables");
        List<ResultRow> results = rr.getResults();
        for (ResultRow row : results) {
            assertFalse(row.getResultColumn(1).getColumnValue() + " should have a value",
                    row.getResultColumn(2).isNull());
        }

        // PE-668 needs this for reports to work
        rr = conn.fetch("show /*!50000 global */ variables");
        results = rr.getResults();
        for (ResultRow row : results) {
            //skip the aws ones
            if (!StringUtils.containsIgnoreCase((String) row.getResultColumn(1).getColumnValue(), "aws")) {
                assertFalse(row.getResultColumn(1).getColumnValue() + " should have a value",
                        row.getResultColumn(2).isNull());
            }
        }
    }

    // when we actually support the where clause - that's the point at which we enable this again
    @Ignore
    @Test
    public void testShowVariablesFilter() throws Throwable {
        ResourceResponse unfiltered = conn.fetch("show variables");
        ResourceResponse filtered = conn.fetch("show variables where Variable_name = 'version_comment'");
        unfiltered.assertEqualResults("testShowVariablesFilter", filtered,
                ComparisonOptions.DEFAULT.withIgnoreOrder());
    }

    @Test
    public void setNamesWithVariousQuotes() throws Throwable {

        // PE-144 support double quotes for set names
        // so support no quotes, single and double quotes
        String charSet = "latin1";
        conn.execute("set names " + charSet);
        conn.assertResults("show session variables like 'character%'",
                br(nr, "character_set_client", charSet, nr, "character_set_connection", charSet, nr,
                        "character_set_database", "utf8", nr, "character_set_results", charSet, nr,
                        "character_set_server", "utf8", nr, "character_set_system", "utf8"));

        charSet = "utf8";
        conn.execute("set names " + "'" + charSet + "'");
        conn.assertResults("show session variables like 'character%'",
                br(nr, "character_set_client", charSet, nr, "character_set_connection", charSet, nr,
                        "character_set_database", "utf8", nr, "character_set_results", charSet, nr,
                        "character_set_server", "utf8", nr, "character_set_system", "utf8"));

        charSet = "ascii";
        conn.execute("set names " + "\"" + charSet + "\"");
        conn.assertResults("show session variables like 'character%'",
                br(nr, "character_set_client", charSet, nr, "character_set_connection", charSet, nr,
                        "character_set_database", "utf8", nr, "character_set_results", charSet, nr,
                        "character_set_server", "utf8", nr, "character_set_system", "utf8"));
    }

    @Test
    public void testPE293() throws Throwable {

        // PE-293 support CHARSET, CHARACTER SET and CHAR SET
        String charSet = "utf8";
        String command = "CHARSET";
        conn.execute("set " + command + " " + charSet);
        conn.assertResults("show variables like 'charset'", br(nr, "charset", charSet));

        charSet = "latin1";
        command = "CHAR SET";
        conn.execute("set " + command + " " + charSet);
        conn.assertResults("show variables like 'charset'", br(nr, "charset", charSet));

        charSet = "ascii";
        command = "CHARACTER SET";
        conn.execute("set " + command + " " + charSet);
        conn.assertResults("show variables like 'charset'", br(nr, "charset", charSet));
    }

    @Test
    public void testShowPlugins() throws Throwable {

        ProxyConnectionResourceResponse rr = (ProxyConnectionResourceResponse) conn.fetch("show plugins");

        // can't check the data returned cause it may vary on different machines
        // will check the header and number of columns
        String[] colHeaders = new String[] { "Name", "Status", "Type", "Library", "License" };
        List<String> returnedColHeaders = new ArrayList<String>();
        ColumnSet cs = rr.getColumns();
        for (int i = 1; i <= cs.size(); i++) {
            returnedColHeaders.add(cs.getColumn(i).getAliasName().trim());
        }
        for (int i = 0; i < colHeaders.length; i++) {
            returnedColHeaders.contains(colHeaders[i]);
            assertTrue("Column " + colHeaders[i] + " must be one of the column headers for show plugins",
                    returnedColHeaders.contains(colHeaders[i]));
        }

        assertTrue("Must have at least one row from a show plugin", rr.getResults().size() > 0);
    }

    @Test
    public void testExtensionComments() throws Throwable {

        conn.execute("/*!40103 set @OLD_TIME_ZONE=@@TIME_ZONE */");
        conn.execute("/*!40103 set TIME_ZONE='+00:30' */");
        conn.assertResults("select @@TIME_ZONE", br(nr, "+00:30"));
        conn.execute("/*!40103 set TIME_ZONE=@OLD_TIME_ZONE */");
        conn.assertResults("select /* comment */ @@TIME_ZONE /* another comment */", br(nr, "+00:00"));

    }

    @Test
    public void testDisabledVariables() throws Throwable {

        // should not be able to set sql_auto_is_null to 1
        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set sql_auto_is_null = 1");
            }
        }.assertError(SchemaException.class, InternalErrors.internalFormatter,
                "Internal error: No support for sql_auto_is_null = 1 (planned)");
        conn.execute("set sql_auto_is_null = 0");
    }

    @Test
    public void testPE378() throws Throwable {
        conn.execute("set sql_safe_updates = 1");
        conn.execute("set sql_safe_updates = 0");
        conn.execute("set sql_safe_updates = 'can be anything cause we ignore this variable'");
    }

    @Test
    public void testSetTxIsolationLevel() throws Throwable {
        final String variableName = "tx_isolation";

        assertVariableValue(variableName, "READ-COMMITTED");
        conn.execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
        assertVariableValue(variableName, "SERIALIZABLE");
        conn.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED");
        assertVariableValue(variableName, "READ-COMMITTED");
    }

    @Test
    public void testSqlModeDefault() throws Throwable {
        final String variableName = "sql_mode";

        // Reset the GLOBAL value first to test for PE-1608.
        assertVariableValue(variableName, "NO_ENGINE_SUBSTITUTION");
        conn.execute("SET GLOBAL sql_mode=default;");
        assertVariableValue(variableName, "NO_ENGINE_SUBSTITUTION");

        conn.execute("SET sql_mode=ALLOW_INVALID_DATES;");
        assertVariableValue(variableName, "NO_ENGINE_SUBSTITUTION,ALLOW_INVALID_DATES");
        conn.execute("SET sql_mode=default;");
        assertVariableValue(variableName, "NO_ENGINE_SUBSTITUTION");
        conn.execute("SET sql_mode=\" NO_AUTO_CREATE_USER , no_engine_substitution , ALLOW_INVALID_DATES \";");
        assertVariableValue(variableName, "NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ALLOW_INVALID_DATES");
        conn.execute("SET sql_mode=\"\";");
        assertVariableValue(variableName, "NO_ENGINE_SUBSTITUTION");
    }

    @Test
    public void testPE1586() throws Throwable {
        assertVariableValue("lower_case_file_system", "OFF");
    }

    @Test
    public void testPE1587() throws Throwable {
        assertVariableValue("have_query_cache", "YES");
    }

    @Test
    public void testPE1589() throws Throwable {
        assertVariableValue("have_ssl", "NO");
        assertVariableValue("have_openssl", "NO");
    }

    @Test
    public void testPE1590() throws Throwable {
        assertVariableValue("thread_handling", "one-thread-per-connection");
    }

    @Test
    public void testPE1609() throws Throwable {
        assertVariableValue("time_format", "%H:%i:%s");
    }

    @Test
    public void testPE1610() throws Throwable {
        assertVariableValue("thread_concurrency", "10");
    }

    @Test
    public void testPE1611() throws Throwable {
        assertVariableValue("log_bin", "OFF");
    }

    @Test
    public void testPE1612() throws Throwable {
        assertVariableValue("license", "AGPL");
    }

    @Test
    public void testPE1613() throws Throwable {
        final String serverVersion = getVariableValue("version");
        assertVariableValue("innodb_version", serverVersion);
    }

    @Test
    public void testPE1614() throws Throwable {
        assertVariableValue("ignore_builtin_innodb", "OFF");
    }

    @Test
    public void testPE1615() throws Throwable {
        assertVariableValue("have_profiling", "NO");
    }

    @Test
    public void testPE1616() throws Throwable {
        assertVariableValue("datetime_format", "%Y-%m-%d %H:%i:%s");
    }

    @Test
    public void testPE1617() throws Throwable {
        assertVariableValue("date_format", "%Y-%m-%d");
    }

    @Test
    public void testPE1618() throws Throwable {
        assertVariableValue("character_set_system", "utf8");
    }

    @Test
    public void testPE1619() throws Throwable {
        assertVariableValue("div_precision_increment", "4");

        conn.assertResults("SELECT 1/7", br(nr, BigDecimal.valueOf(0.1429)));

        conn.execute("set session div_precision_increment = 12");

        conn.assertResults("SELECT 1/7", br(nr, BigDecimal.valueOf(0.142857142857)));
    }

    @Test
    public void testPE1620() throws Throwable {
        assertVariableValue("default_week_format", "0");

        conn.assertResults("SELECT WEEK('2008-02-20')", br(nr, 7L));

        conn.execute("set session default_week_format = 1");

        conn.assertResults("SELECT WEEK('2008-02-20')", br(nr, 8L));
        conn.assertResults("SELECT WEEK('2008-12-31')", br(nr, 53L));
    }

    @Test
    public void testPE1621() throws Throwable {
        assertVariableValue("version_compile_machine", "64-bit");
    }

    @Test
    public void testPE1603() throws Throwable {
        assertTimestampValue(2, null);

        conn.execute("set session timestamp = 10");

        assertTimestampValue(2, 10l);

        conn.execute("set session timestamp = 0");

        assertTimestampValue(2, null);

        conn.execute("set session timestamp = 300000000");

        assertTimestampValue(2, 300000000l);

        conn.execute("set session timestamp = DEFAULT");

        assertTimestampValue(2, null);

        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set session timestamp = '10'");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongTypeForVariable, "timestamp");

        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set session timestamp = 'DEFAULT'");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongTypeForVariable, "timestamp");
    }

    private void assertTimestampValue(final int waitTimeSec, final Long expected) throws Throwable {
        final Long value1 = Long.parseLong(getVariableValue("timestamp"));
        Thread.sleep(Long.valueOf(1000 * waitTimeSec));
        final Long value2 = Long.parseLong(getVariableValue("timestamp"));

        if (expected != null) {
            //this timestamp checks are simple, because set timestamp whould fix the returned value of NOW()
            conn.assertResults("SELECT UNIX_TIMESTAMP(NOW())", br(nr, expected));
            assertEquals(expected, value1);
            assertEquals(expected, value2);
        } else {
            //these timestamp checks have to account for clock and execution drift, since the timestamp is set to DEFAULT.

            assertTimestampBetween(value1, value1 + waitTimeSec, value2, /*allowed width*/waitTimeSec + 2);//allow for 2 seconds of variation, Thread.sleep(2000) could easily measure as 3 seconds.

            long beforeTime = TimestampVariableUtils.getCurrentSystemTime();
            ResourceResponse queriedTimestamp = conn.fetch("SELECT UNIX_TIMESTAMP(NOW())");
            long afterTime = TimestampVariableUtils.getCurrentSystemTime();
            List<ResultRow> rows = queriedTimestamp.getResults();
            assertEquals(1, rows.size());
            final ResultRow row = rows.get(0);
            final List<ResultColumn> resultColumns = row.getRow();
            assertEquals(1, resultColumns.size());
            ResultColumn col = resultColumns.get(0);
            long entry = (Long) col.getColumnValue();

            assertTimestampBetween(beforeTime, entry, afterTime, 1);

        }
    }

    private void assertTimestampBetween(long before, long middle, long after, long allowedWidth) {
        assertTrue("before = " + before + " , middle= " + middle, before <= middle);
        assertTrue("middle = " + middle + " , after = " + after, middle <= after);
        assertTrue("before = " + before + " , after = " + after + " , allowedWidth = " + allowedWidth,
                after - before <= allowedWidth);
    }

    @Test
    public void testPE1639() throws Throwable {
        assertVariableValue("innodb_adaptive_flushing", "ON");
    }

    @Test
    public void testPE1640() throws Throwable {
        assertVariableValue("innodb_fast_shutdown", "1");
    }

    @Test
    public void testPE1641() throws Throwable {
        assertVariableValue("innodb_mirrored_log_groups", "1");
    }

    @Test
    public void testPE1642() throws Throwable {
        assertVariableValue("innodb_stats_method", "nulls_equal");
    }

    @Test
    public void testPE1643() throws Throwable {
        final String tspValue = getVariableValue("innodb_stats_transient_sample_pages");
        assertVariableValue("innodb_stats_sample_pages", tspValue);
    }

    @Test
    public void testPE1644() throws Throwable {
        assertVariableValue("innodb_stats_transient_sample_pages", "8");
    }

    @Test
    public void testPE1645() throws Throwable {
        assertVariableValue("innodb_table_locks", "ON");
    }

    @Test
    public void testPE1646() throws Throwable {
        assertVariableValue("myisam_use_mmap", "OFF");
    }

    @Test
    public void testPE1647() throws Throwable {
        assertVariableValue("tmp_table_size", "16777216");
    }

    @Test
    public void testPE1649() throws Throwable {
        assertVariableValue("skip_networking", "OFF");
    }

    @Test
    public void testPE1650() throws Throwable {
        assertVariableValue("protocol_version", "10");
    }

    @Test
    public void testPE1656() throws Throwable {
        assertVariableValue("backend_wait_timeout", "28800");
        assertVariableValue("wait_timeout", "28800");

        conn.execute("set wait_timeout = 14400");
        assertVariableValue("backend_wait_timeout", "28800");
        assertVariableValue("wait_timeout", "14400");
    }

    @Test
    public void testPE1659() throws Throwable {
        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set global wait_timeout = 'blah'");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongTypeForVariable, "wait_timeout");

        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set global sql_auto_is_null = 2");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongValueForVariable, "sql_auto_is_null", "2");

        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set global sql_auto_is_null = 'blah'");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongValueForVariable, "sql_auto_is_null", "blah");

        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set global tx_isolation = 'blah'");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongValueForVariable, "tx_isolation", "blah");

        new ExpectedSqlErrorTester() {
            @Override
            public void test() throws Throwable {
                conn.execute("set global tx_isolation = NULL");
            }
        }.assertError(SchemaException.class, MySQLErrors.wrongValueForVariable, "tx_isolation", "NULL");

        // Test out-of-range value handling.
        // They should be replaced by the nearest limit.
        // Valid 'div_precision_increment' values are: 0 - 30
        assertVariableValue("div_precision_increment", "4");

        conn.execute("set div_precision_increment = -1");
        assertVariableValue("div_precision_increment", "0");

        conn.execute("set div_precision_increment = 35");
        assertVariableValue("div_precision_increment", "30");

        conn.execute("set div_precision_increment = DEFAULT");
        assertVariableValue("div_precision_increment", "4");

    }

    @Test
    public void testPE1662() throws Throwable {

        // SESSION values
        assertVariableValue("character_set_connection", "utf8");
        assertVariableValue("collation_connection", "utf8_general_ci");

        conn.execute("set character_set_connection = ascii");

        assertVariableValue("character_set_connection", "ascii");
        assertVariableValue("collation_connection", "ascii_general_ci");

        conn.execute("set collation_connection = utf8_general_ci");

        assertVariableValue("character_set_connection", "utf8");
        assertVariableValue("collation_connection", "utf8_general_ci");

        // GLOBAL values
        assertGlobalVariableValue("character_set_connection", "utf8");
        assertGlobalVariableValue("collation_connection", "utf8_general_ci");

        conn.execute("set global character_set_connection = ascii");

        assertGlobalVariableValue("character_set_connection", "ascii");
        assertGlobalVariableValue("collation_connection", "ascii_general_ci");

        conn.execute("set global collation_connection = utf8_general_ci");

        assertGlobalVariableValue("character_set_connection", "utf8");
        assertGlobalVariableValue("collation_connection", "utf8_general_ci");
    }

    private void assertVariableValue(final String variableName, final Object expected) throws Throwable {
        conn.assertResults("show variables like '" + variableName + "'", br(nr, variableName, expected));
    }

    private void assertGlobalVariableValue(final String variableName, final Object expected) throws Throwable {
        conn.assertResults("show global variables like '" + variableName + "'", br(nr, variableName, expected));
    }

    private String getVariableValue(final String variableName) throws Throwable {
        final List<ResultRow> rows = conn.fetch("show variables like '" + variableName + "'").getResults();
        assertEquals("Exactly one result row expected for variable '" + variableName + "'.", 1, rows.size());

        final ResultRow row = rows.get(0);
        final List<ResultColumn> resultColumns = row.getRow();
        if (resultColumns.size() != 2) {
            throw new PECodingException("\"SHOW VARIABLES LIKE ...\" should return exactly two columns.");
        }
        assertEquals("Wrong variable name returned for '" + variableName + "'.", variableName,
                resultColumns.get(0).getColumnValue());

        return (String) resultColumns.get(1).getColumnValue();
    }

    // this is a cheesy test
    @Test
    public void testDynamicAdd() throws Throwable {
        String infoQuery = "select * from information_schema.variable_definitions where name like 'fromage'";
        conn.assertResults(infoQuery, br());
        conn.execute(
                "alter dve add variable fromage scopes='SESSION,GLOBAL' valuetype='varchar' value='blue' options='NULLABLE' description='cheese!'");
        conn.assertResults(infoQuery,
                br(nr, "fromage", "blue", "varchar", "SESSION,GLOBAL", "NULLABLE", "cheese!"));
        ProxyConnectionResource nonRoot = new ProxyConnectionResource(nonRootUser, nonRootUser);
        try {
            nonRoot.assertResults("show variables like 'fromage'", br(nr, "fromage", "blue"));
        } finally {
            nonRoot.close();
        }
    }

    @Test
    public void testDocStrings() throws Throwable {
        ResourceResponse rr = conn
                .execute("select name from information_schema.variable_definitions where description = ''");
        List<String> missing = new ArrayList<String>();
        for (ResultRow row : rr.getResults()) {
            missing.add((String) row.getResultColumn(1).getColumnValue());
        }
        if (!missing.isEmpty())
            fail("Empty variable docstrings: " + Functional.join(missing, ","));
        //      System.out.println(conn.printResults("select * from information_schema.variable_definitions"));
    }

    private static String getCurrentGlobalValue(DBHelper helper, String varName) throws Throwable {
        ResultSet rs = null;
        String out = null;
        try {
            if (helper.executeQuery("select @@global." + varName)) {
                rs = helper.getResultSet();
                if (!rs.next())
                    fail("Variable " + varName + " apparently does not exist on native");
                out = rs.getString(1);
            }
        } finally {
            if (rs != null)
                rs.close();
        }
        return out;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Test
    public void testGlobalPushdown() throws Throwable {
        VariableManager vm = Singletons.require(VariableService.class).getVariableManager();
        DBHelper helper = null;

        String execFormat = "set global %s = %s";

        try {
            helper = buildHelper();
            for (VariableHandler vh : vm.getGlobalHandlers()) {
                if (!vh.isEmulatedPassthrough())
                    continue;
                Object defVal = vh.getDefaultOnMissing();
                if (defVal == null)
                    continue;
                String currentGlobal = getCurrentGlobalValue(helper, vh.getName());
                String setTo = vh.toExternal(defVal);
                conn.execute(String.format(execFormat, vh.getName(), setTo));
                String newGlobal = getCurrentGlobalValue(helper, vh.getName());
                assertEquals("should have same value for " + vh.getName(), PEStringUtils.dequote(setTo), newGlobal);
                Object oldGlobalConverted = vh.toInternal(currentGlobal);
                String oldGlobalExternal = vh.toExternal(oldGlobalConverted);
                helper.executeQuery("set global " + vh.getName() + " = " + oldGlobalExternal);
            }
        } finally {
            helper.disconnect();
        }

    }

    @Test
    public void testAccess() throws Throwable {
        VariableManager vm = Singletons.require(VariableService.class).getVariableManager();
        testAccess(vm.lookupMustExist(null, "tx_isolation"),
                new Values("REPEATABLE-READ", "SERIALIZABLE", "READ-COMMITTED"));
        testAccess(vm.lookupMustExist(null, "adaptive_cleanup_interval"), new Values("1000", "5000", "10000"));
        testAccess(vm.lookupMustExist(null, "cost_based_planning"), new Values("YES", "NO"));
        testAccess(vm.lookupMustExist(null, "debug_context"), new Values("YES", "NO"));
    }

    private static final VariableRoundTrip[] globals = new VariableRoundTrip[] {
            new VariableRoundTrip("set global %s = %s", "show global variables like '%s'") {

                public Object[] buildExpectedResults(String varName, String varValue) {
                    return br(nr, varName, varValue);
                }
            }, new VariableRoundTrip("set @@global.%s = %s",
                    "select variable_value from information_schema.global_variables where variable_name like '%s'") };

    private static final VariableRoundTrip[] sessions = new VariableRoundTrip[] {
            new VariableRoundTrip("set %s = %s", "show session variables like '%s'") {
                public Object[] buildExpectedResults(String varName, String varValue) {
                    return br(nr, varName, varValue);
                }

            },
            new VariableRoundTrip("set @@session.%s = %s",
                    "select variable_value from information_schema.session_variables where variable_name like '%s'"),
            new VariableRoundTrip("set @@%s = %s",
                    "select variable_value from information_schema.session_variables where variable_name like '%s'"),
            new VariableRoundTrip("set @@local.%s = %s",
                    "select variable_name, variable_value from information_schema.session_variables where variable_name = '%s'") {
                public Object[] buildExpectedResults(String varName, String varValue) {
                    return br(nr, varName, varValue);
                }
            } };

    private static final String sessQuery = "select variable_value from information_schema.session_variables where variable_name = '%s'";

    @SuppressWarnings("rawtypes")
    private void testAccess(VariableHandler handler, Values values) throws Throwable {
        ProxyConnectionResource nonRoot = new ProxyConnectionResource(nonRootUser, nonRootUser);
        try {
            // before we start modifying the global values, save off the session value to validate
            // that it does not change
            String sessVal = null;
            if (handler.getScopes().contains(VariableScopeKind.SESSION))
                sessVal = getSessionValue(nonRoot, handler);
            if (handler.getScopes().contains(VariableScopeKind.GLOBAL)) {
                // make sure all the root versions work
                for (VariableRoundTrip vrt : globals) {
                    roundTrip(conn, handler, vrt, values);
                }
                // now, for each root version, make sure a nonroot user cannot set it
                for (VariableRoundTrip vrt : globals) {
                    try {
                        nonRoot.execute(vrt.buildSet(handler.getName(), values.current(),
                                handler.getMetadata().isNumeric()));
                        fail("non root should not be able to set global value of " + handler.getName());
                    } catch (PEException pe) {
                        if (!pe.getMessage().startsWith("Must be root"))
                            throw pe;
                    }
                }
            }
            if (handler.getScopes().contains(VariableScopeKind.SESSION)) {
                String stillValue = getSessionValue(nonRoot, handler);
                assertEquals("sess value should not change", sessVal, stillValue);

                // make sure both root and nonroot can modify session values
                for (VariableRoundTrip vrt : sessions) {
                    roundTrip(conn, handler, vrt, values);
                }
                for (VariableRoundTrip vrt : sessions) {
                    roundTrip(nonRoot, handler, vrt, values);
                }
            }
        } finally {
            nonRoot.disconnect();
        }
    }

    @SuppressWarnings("rawtypes")
    private String getSessionValue(ProxyConnectionResource conn, VariableHandler handler) throws Throwable {
        ResourceResponse rr = conn.execute(String.format(sessQuery, handler.getName()));
        return (String) rr.getResults().get(0).getRow().get(0).getColumnValue();
    }

    @SuppressWarnings("rawtypes")
    private void roundTrip(ProxyConnectionResource proxyConn, VariableHandler handler, VariableRoundTrip vrt,
            Values values) throws Throwable {
        conn.execute(vrt.buildSet(handler.getName(), values.next(), handler.getMetadata().isNumeric()));
        conn.assertResults(vrt.buildQuery(handler.getName()),
                vrt.buildExpectedResults(handler.getName(), values.current()));
    }

    private static class Values {

        private final String[] values;
        private int index = -1;

        public Values(String... myValues) {
            values = myValues;
            index = -1;
        }

        public String next() {
            if (++index >= values.length)
                index = 0;
            return values[index];
        }

        public String current() {
            return values[index];
        }
    }

    private static class VariableRoundTrip {

        private String setFormat;
        private String queryFormat;

        public VariableRoundTrip(String setFormat, String queryFormat) {
            this.setFormat = setFormat;
            this.queryFormat = queryFormat;
        }

        public String buildSet(String varName, String varValue, boolean isNumeric) {
            return String.format(setFormat, varName, (isNumeric) ? varValue : PEStringUtils.singleQuote(varValue));
        }

        public String buildQuery(String varName) {
            return String.format(queryFormat, varName);
        }

        public Object[] buildExpectedResults(String varName, String varValue) {
            return br(nr, varValue);
        }

    }

}