/* 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.nio.ByteBuffer; import java.util.Arrays; import org.junit.Test; import org.voltdb.BackendTarget; import org.voltdb.VoltTable; import org.voltdb.client.Client; import org.voltdb.client.ClientResponse; import org.voltdb.compiler.VoltProjectBuilder; public class TestHexLiteralsSuite extends RegressionSuite { /* * Test hex literals in VARBINARY contexts. * * Test hex literals as integers: * - In arithmetic (integer type should be inferred) * - In relational operators * - In the bitwise functions */ static private long[] interestingValues = new long[] { Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 1000, -1, 0, 1, 1000, Long.MAX_VALUE -1, Long.MAX_VALUE }; // Some values that we can do arithmetic on, // without overflowing to different types. // 3 billion is roughly the square root of 2^63 - 1. static private long[] boringValues = new long[] { -3037000400L, 1500000000, 1024, -1, 0, 1, 16, 2600000075L, 3037000400L }; private static String longToHexLiteral(long val) { return "X'" + makeEvenDigits(val) + "'"; } private static String longToEightByteHexLiteral(long val) { return "X'" + makeSixteenDigits(val) + "'"; } private static String makeEvenDigits(long val) { String valAsHex = Long.toHexString(val).toUpperCase(); if ((valAsHex.length() % 2) == 1) { valAsHex = "0" + valAsHex; } return valAsHex; } private static String makeSixteenDigits(long val) { String valAsHex = Long.toHexString(val).toUpperCase(); if (valAsHex.length() < 16) { int numZerosNeeded = 16 - valAsHex.length(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < numZerosNeeded; ++i) { sb.append('0'); } sb.append(valAsHex); valAsHex = sb.toString(); } return valAsHex; } @Test public void testVarbinaryHexLiteralsAsParams() throws Exception { Client client = getClient(); for (int i = 0; i < interestingValues.length; ++i) { long val = interestingValues[i]; client.callProcedure("InsertVarbinary", i, longToEightByteHexLiteral(val)); // Verify that the right constant was inserted VoltTable vt = client.callProcedure("@AdHoc", "select vb from t where pk = ?", i) .getResults()[0]; assertTrue(vt.advanceRow()); assertTrue(Arrays.equals(longToBytes(val), vt.getVarbinary(0))); } // Mixed case literals are okay. ClientResponse cr = client.callProcedure("InsertVarbinary", 20, "X'AaBbCcDdEeFf'"); assertEquals(ClientResponse.SUCCESS, cr.getStatus()); // Must be an even number of digits verifyProcFails(client, "String is not properly hex-encoded", "InsertVarbinary", 21, "x'F'"); verifyProcFails(client, "String is not properly hex-encoded", "InsertVarbinary", 21, "x'XYZZY'"); // Too many digits for type verifyProcFails(client, "The size 9 of the value exceeds the size of the VARBINARY\\(8\\) column", "InsertVarbinary", 21, "x'FfffFfffFfffFfffFf'"); } private static byte[] longToBytes(long x) { ByteBuffer buffer = ByteBuffer.allocate(8); buffer.putLong(x); return buffer.array(); } @Test public void testVarbinaryProcsWithEmbeddedLiterals() throws Exception { Client client = getClient(); // Insert all the interesting values in the table, // and make sure we can select them back with a constant // embedded in the where clause for (int i = 0; i < interestingValues.length; ++i) { client.callProcedure("InsertVarbinary", i, longToBytes(interestingValues[i])); } for (int i = 0; i < interestingValues.length; ++i) { long val = interestingValues[i]; String digits = makeSixteenDigits(val); String procName = "XQUOTE_VARBINARY_PROC_" + digits; VoltTable vt = client.callProcedure(procName).getResults()[0]; validateRowOfLongs(vt, new long[] {i}); } } @Test public void testIntegerHexLiteralsAsParams() throws Exception { Client client = getClient(); for (int i = 0; i < interestingValues.length; ++i) { long val = interestingValues[i]; client.callProcedure("InsertBigint", i, longToHexLiteral(val)); // Verify that the right constant was inserted VoltTable vt = client.callProcedure("@AdHoc", "select bi from t where pk = ?", i) .getResults()[0]; validateRowOfLongs(vt, new long[] {val}); } // 0 digits is not a valid value verifyProcFails(client, "Unable to convert string x'' to long value for target parameter", "InsertBigint", 21, "x''"); // Too many digits (more than 16) won't fit into a BIGINT. verifyProcFails(client, "Unable to convert string x'FFFFffffFFFFffffF' to long value for target parameter", "InsertBigint", 21, "x'FFFFffffFFFFffffF'"); } @Test public void testIntegerProcsWithEmbeddedHexLiteralsSelect() throws Exception { Client client = getClient(); // Insert one row, so calls below produce just one row. client.callProcedure("InsertBigint", 0, 0); // For each interesting value, // invoke a corresponding procedure that does XOR against that value. // Make sure that the literal in the procedure was interpreted correctly. for (long val : interestingValues) { VoltTable result = client.callProcedure("INT_HEX_LITERAL_PROC_" + makeEvenDigits(val), 0xF0F0F0F0F0F0F0F0L) .getResults()[0]; assertTrue(result.advanceRow()); String actual = result.getString(0); long expectedNum = val ^ 0xF0F0F0F0F0F0F0F0L; if (expectedNum != Long.MIN_VALUE && val != Long.MIN_VALUE) { String expected = Long.toHexString(expectedNum).toUpperCase(); assertEquals(expected, actual); } else { // Input or output to bit op was null value // So output of HEX will be null. assertEquals(null, actual); } } } @Test public void testIntegerProcsWithEmbeddedLiteralsWhere() throws Exception { Client client = getClient(); // Insert all the interesting values in the table, // and make sure we can select them back with a constant // embedded in the where clause for (int i = 0; i < interestingValues.length; ++i) { client.callProcedure("InsertBigint", i, interestingValues[i]); } for (int i = 0; i < interestingValues.length; ++i) { long val = interestingValues[i]; String procName = "INT_HEX_LITERAL_PROC_WHERE_" + makeEvenDigits(interestingValues[i]); VoltTable vt = client.callProcedure(procName).getResults()[0]; if (val != Long.MIN_VALUE) { assertTrue(vt.advanceRow()); assertEquals(i, vt.getLong(0)); } else { assertFalse(vt.advanceRow()); } } } @Test public void testIntegerHexLiteralsInArithmetic() throws Exception { Client client = getClient(); // Insert one row, so calls below produce just one row. client.callProcedure("InsertBigint", 0, 0); for (long procVal : boringValues) { String procName = "HEX_LITERAL_PROC_ARITH_" + makeEvenDigits(procVal); for (long paramVal : boringValues) { VoltTable actual = client.callProcedure(procName, paramVal, paramVal, paramVal, paramVal == 0 ? 1 : paramVal) // avoid divide by zero .getResults()[0]; long[] expected = { procVal + paramVal, procVal - paramVal, procVal * paramVal, procVal / (paramVal == 0 ? 1 : paramVal), -procVal}; validateRowOfLongs(actual, expected); } } } // Users may want to initialize or update narrower integer fields (TINYINT, SMALLINT, BIGINT) // with hexadecimal literals. The tests below just use TINYINT as a representative // of the non-BIGINT type class. @Test public void testIntegerHexLiteralInsertTinyintParams() throws Exception { Client client = getClient(); int pk = 0; client.callProcedure("InsertTinyint", pk, "x'00'"); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {0x00}); ++pk; client.callProcedure("InsertTinyint", pk, "x'7F'"); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {0x7F}); ++pk; // This is -127, the smallest allowed value. client.callProcedure("InsertTinyint", pk, "x'FfffFfffFfffFf81'"); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {0xFfffFfffFfffFf81L}); ++pk; verifyProcFails(client, "Type BIGINT with value 255 can't be cast as TINYINT " + "because the value is out of range", "InsertTinyint", pk, "x'FF'"); verifyProcFails(client, "Type BIGINT with value 128 can't be cast as TINYINT " + "because the value is out of range", "InsertTinyint", pk, "x'80'"); // -128 fits into a signed byte but is our null value and therefore out of range verifyProcFails(client, "Type BIGINT with value -128 can't be cast as TINYINT " + "because the value is out of range", "InsertTinyint", pk, "x'FfffFfffFfffFf80'"); } @Test public void testIntegerHexLiteralInsertTinyintConstants() throws Exception { Client client = getClient(); int pk = 0; client.callProcedure("InsertTinyintConstantMin", pk); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {-127}); ++pk; client.callProcedure("InsertTinyintConstantMax", pk); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {127}); ++pk; } @Test public void testIntegerHexLiteralUpdateTinyintParams() throws Exception { Client client = getClient(); int pk = 37; client.callProcedure("InsertTinyint", pk, "x'00'"); client.callProcedure("UpdateTinyint", "X'7F'", pk); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {0x7F}); client.callProcedure("UpdateTinyint", "X'FfffFfffFfffFf81'", pk); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {-127}); verifyProcFails(client, "Type BIGINT with value 255 can't be cast as TINYINT " + "because the value is out of range", "UpdateTinyint", "x'FF'", pk); verifyProcFails(client, "Type BIGINT with value 128 can't be cast as TINYINT " + "because the value is out of range", "UpdateTinyint", "x'80'", pk); // -128 fits into a signed byte but is our null value and therefore out of range verifyProcFails(client, "Type BIGINT with value -128 can't be cast as TINYINT " + "because the value is out of range", "UpdateTinyint", "x'FFFFFFFFFFFFFF80'", pk); } @Test public void testIntegerHexLiteralUpdateTinyintConstants() throws Exception { Client client = getClient(); int pk = 37; client.callProcedure("InsertTinyint", pk, "x'3F'"); client.callProcedure("UpdateTinyintConstantMin", pk); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {-127}); client.callProcedure("UpdateTinyintConstantMax", pk); validateTableOfScalarLongs(client, "select ti from t where pk = " + pk, new long[] {127}); } @Test public void testIntegerHexLiteralMixedMath() throws Exception { Client client = getClient(); // Insert one row, so calls below produce just one row. client.callProcedure("InsertBigint", 0, 0); for (long val : boringValues) { System.out.println(" *** " + val); VoltTable vt = client.callProcedure("MixedTypeMath", val, val, val, val) .getResults()[0]; assertTrue(vt.advanceRow()); assertEquals(val + 33, vt.getLong(0)); assertEquals(val + 33.0, vt.getDouble(1)); assertEquals(val + 33, vt.getLong(2)); assertEquals(10000000000000000033.0 + val, vt.getDouble(3)); String hexVal = longToHexLiteral(val); vt = client.callProcedure("MixedTypeMath", hexVal, val, hexVal, val) .getResults()[0]; assertTrue(vt.advanceRow()); assertEquals(val + 33, vt.getLong(0)); assertEquals(val + 33.0, vt.getDouble(1)); assertEquals(val + 33, vt.getLong(2)); assertEquals(10000000000000000033.0 + val, vt.getDouble(3)); } // When parameters are typed as double, you can't pass an x-literal to them. verifyProcFails(client, "Unable to convert string X'21' to double value for target parameter", "MixedTypeMath", "X'21'", "X'21'", "X'21'", "X'21'"); } @Test public void testIntegerHexLiteralDefaultValues() throws Exception { Client client = getClient(); // Insert one row, so calls below produce just one row. client.callProcedure("@AdHoc", "insert into t_defaults (pk) values (0);"); validateTableOfLongs(client, "select * from t_defaults;", new long[][] {{ 0, 0, 127, -127, -127, 127, 109, -109, 0, Long.MAX_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 1, Long.MAX_VALUE, 1000001, -1000001 }}); } // // JUnit / RegressionSuite boilerplate // public TestHexLiteralsSuite(String name) { super(name); } static public junit.framework.Test suite() { VoltServerConfig config = null; MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestHexLiteralsSuite.class); boolean success; VoltProjectBuilder project = new VoltProjectBuilder(); String literalSchema = "CREATE TABLE T (\n" + " PK INTEGER NOT NULL PRIMARY KEY,\n" + " BI BIGINT,\n" + " TI TINYINT,\n" + " VB VARBINARY(8)\n" + ");\n" ; literalSchema += "CREATE TABLE T_DEFAULTS (\n" + " PK INTEGER NOT NULL PRIMARY KEY,\n" + " TI1 TINYINT DEFAULT X'00',\n" + " TI2 TINYINT DEFAULT X'7F',\n" // max for type + " TI3 TINYINT DEFAULT X'FfffFfffFfffFf81',\n" // min for type + " TI4 TINYINT DEFAULT -X'7F',\n" // min for type, using unary minus + " TI5 TINYINT DEFAULT -X'FfffFfffFfffFf81',\n" // max for type, using unary minus + " TI6 TINYINT DEFAULT X'6D',\n" // decimal 109 + " TI7 TINYINT DEFAULT -X'6D',\n" // decimal -109 + " BI1 BIGINT DEFAULT X'00',\n" + " BI2 BIGINT DEFAULT X'7FFFFFFFFFFFFFFF',\n" // max for type + " BI3 BIGINT DEFAULT X'8000000000000001',\n" // min for type + " BI4 BIGINT DEFAULT -X'7FFFFFFFFFFFFFFF',\n" // min for type, with unary minus + " BI5 BIGINT DEFAULT -X'8000000000000001',\n" // min for type with unary minus + " BI6 BIGINT DEFAULT X'0F4241',\n" // decimal 1,000,001 + " BI7 BIGINT DEFAULT -X'0F4241',\n" // decimal -1,000,001 + ");\n" ; literalSchema += "CREATE PROCEDURE InsertVarbinary AS " + "INSERT INTO T (PK, VB) VALUES (?, ?);"; literalSchema += "CREATE PROCEDURE InsertBigint AS " + "INSERT INTO T (PK, BI) VALUES (?, ?);"; literalSchema += "CREATE PROCEDURE InsertTinyint AS " + "INSERT INTO T (PK, TI) VALUES (?, ?);"; literalSchema += "CREATE PROCEDURE UpdateTinyint AS " + "UPDATE T SET TI = ? WHERE PK = ?;"; literalSchema += "CREATE PROCEDURE InsertTinyintConstantMax AS " + "INSERT INTO T (PK, TI) VALUES (?, X'7F');"; literalSchema += "CREATE PROCEDURE InsertTinyintConstantMin aS " + "INSERT INTO T (PK, TI) VALUES (?, X'FfffFfffFfffFf81');"; literalSchema += "CREATE PROCEDURE UpdateTinyintConstantMax AS " + "UPDATE T SET TI = X'7F' WHERE PK = ?;"; literalSchema += "CREATE PROCEDURE UpdateTinyintConstantMin AS " + "UPDATE T SET TI = X'FfffFfffFfffFf81' WHERE PK = ?;"; literalSchema += "CREATE PROCEDURE MixedTypeMath AS \n" + "SELECT\n" + " 33 + ?,\n" // + " 33.0 + ?,\n" + " X'21' + ?,\n" + " 10000000000000000033 + ?" + "FROM T;" + ""; for (long val : interestingValues) { literalSchema += "CREATE PROCEDURE XQUOTE_VARBINARY_PROC_" + makeSixteenDigits(val) + " AS\n" + " SELECT PK FROM T WHERE VB = " + longToEightByteHexLiteral(val) + ";\n"; } // Create a bunch of procedures with various embedded literals // in the select list for (long val : interestingValues) { literalSchema += "CREATE PROCEDURE INT_HEX_LITERAL_PROC_" + makeEvenDigits(val) + " AS\n" + " SELECT HEX(BITXOR(X'" + makeEvenDigits(val) + "', ?)) FROM T;\n"; } // Create a bunch of procedures with various embedded literals // in the where clause for (long val : interestingValues) { literalSchema += "CREATE PROCEDURE INT_HEX_LITERAL_PROC_WHERE_" + makeEvenDigits(val) + " AS\n" + " SELECT PK FROM T WHERE BI = X'" + makeEvenDigits(val) + "';\n"; } // Create a bunch of procedures with various embedded literals // in arithmetic expressions for (long val : boringValues) { literalSchema += "CREATE PROCEDURE HEX_LITERAL_PROC_ARITH_" + makeEvenDigits(val) + " AS\n" + " SELECT \n" + "? + X'" + makeEvenDigits(val) + "',\n" + "X'" + makeEvenDigits(val) + "' - ?,\n" + "? * X'" + makeEvenDigits(val) + "',\n" + "X'" + makeEvenDigits(val) + "' / ?,\n" + "- X'" + makeEvenDigits(val) + "'\n" + "FROM T;"; } try { project.addLiteralSchema(literalSchema); } catch (Exception e) { fail(); } config = new LocalCluster("fixedsql-threesite.jar", 3, 1, 0, BackendTarget.NATIVE_EE_JNI); success = config.compile(project); assertTrue(success); builder.addServerConfig(config); return builder; } }