/* 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.math.BigDecimal;
import org.voltdb.BackendTarget;
import org.voltdb.VoltType;
import org.voltdb.client.Client;
import org.voltdb.client.ProcCallException;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb_testprocs.regressionsuites.failureprocs.ProcToTestTypeConversion;
public class TestTypeConversionSuite extends RegressionSuite {
// column types of table T in order they are defined
private static final VoltType[] m_tableColTypeVal = {
VoltType.TINYINT,
VoltType.SMALLINT,
VoltType.INTEGER,
VoltType.BIGINT,
VoltType.FLOAT,
VoltType.DECIMAL,
VoltType.TIMESTAMP,
VoltType.STRING,
VoltType.VARBINARY,
VoltType.GEOGRAPHY_POINT,
VoltType.GEOGRAPHY,
};
private static final String m_javaTypeNamePatternForInsertTest[] = {
"Byte",
"Short",
"Integer",
"Long",
"Double",
"BigDecimal",
"TimestampType",
"String",
"byte\\[\\]",
"GeographyPointValue",
"GeographyValue",
};
private static final VoltType[] m_tableColInListTypeVal = {
VoltType.INLIST_OF_BIGINT,
VoltType.INLIST_OF_BIGINT,
VoltType.INLIST_OF_BIGINT,
VoltType.INLIST_OF_BIGINT,
null, // IN LIST of FLOAT not supported
null, // IN LIST of DECIMAL not supported
VoltType.INLIST_OF_BIGINT,
VoltType.INLIST_OF_STRING,
null, // IN LIST of VARBINARY not supported
null, // IN LIST of GEOGRAPHY_POINT not supported
null, // IN LIST of GEOGRAPHY not supported
};
private static final String m_javaTypeNamePatternForInListTest[] = {
// Interpretation of byte[] as a list of tiny int is prevented
// by an overriding interpretation of byte[] as a VARBINARY value.
// So, it can not be supported,
// unless/until we find a way around that interpretation.
"byte\\[\\]",
"short\\[\\]",
"int\\[\\]",
"long\\[\\]",
"double\\[\\]",
"BigDecimal\\[\\]",
"TimestampType\\[\\]",
"String\\[\\]",
"byte\\[\\]\\[\\]",
"GeographyPointValue\\[\\]",
"GeographyValue\\[\\]",
};
// A list of non-array types that should all fail to type-check when passed
// to "IN ?" parameters that expect some kind of array argument.
private static final String m_javaTypeNamePatternForInListFailureTest[] = {
"Byte",
"Short",
"Integer",
"Long",
"Double",
"BigDecimal",
"TimestampType",
"String",
// Interpretation of byte[] as a list of tiny int is prevented
// by an overriding interpretation of byte[] as a VARBINARY value.
// So, it can not be supported,
// unless/until we find a way around that interpretation.
// Expect the error message for this case to look a little different.
"byte\\[\\]",
"GeographyPointValue",
"GeographyValue",
};
// Row index provides the type converting "from"
// Column index provides the type converting "to"
// To see type for column (to) or row (from), map it's
// index m_tableColTypeVal to get it's type.
private static final boolean [][] m_typeConversionMatrix = {
/* To:
tiny small int bigint float decimal ts string binary point polygon // From: */
{true, true, true, true, true, true, true, true, false, false, false}, // TinyInt
{true, true, true, true, true, true, true, true, false, false, false}, // SmallInt
{true, true, true, true, true, true, true, true, false, false, false}, // Integer
{true, true, true, true, true, true, true, true, false, false, false}, // BigInt
{true, true, true, true, true, true, true, true, false, false, false}, // Float
{true, true, true, true, true, true, true, true, false, false, false}, // Decimal
{true, true, true, true, true, false, true, true, false, false, false}, // TIMESTAMP
{true, true, true, true, true, true, true, true, false, false, false}, // VARCHAR/STRING
{false, false, false, false, false, false, false, true, true, false, false}, // VARBINARY
{false, false, false, false, false, false, false, false, false, true, false}, // POINT
{false, false, false, false, false, false, false, false, false, false, true}, // POLYGON
};
// Row index provides the type converting "from" (user supplied data of certain type).
// Column index provides the type converting "to" (the actual column type in table).
// To see type for column (to) or row (from), map it's index m_tableColTypeVal to get it's
// type this is based on allowed type conversion for comparison which in list param arguments uses
private static final boolean [][] m_typeConversionMatrixInList = {
/* To:
tiny small int bigint float decimal ts string binary point polygon // From: */
{true, true, true, true, false, false, true, false, false, false, false}, // TinyInt
{true, true, true, true, false, false, true, false, false, false, false}, // SmallInt
{true, true, true, true, false, false, true, false, false, false, false}, // Integer
{true, true, true, true, false, false, true, false, false, false, false}, // BigInt
{false, false, false, false, false, false, false, false, false, false, false}, // Float
{false, false, false, false, false, false, false, false, false, false, false}, // Decimal
{false, false, false, false, false, false, false, false, false, false, false}, // TIMESTAMP
{false, false, false, false, false, false, false, true, false, false, false}, // VARCHAR/STRING
{false, false, false, false, false, false, false, false, true, false, false}, // VARBINARY
{false, false, false, false, false, false, false, false, false, true, false}, // POINT
{false, false, false, false, false, false, false, false, false, false, true}, // POLYGON
};
public TestTypeConversionSuite(String name) {
super(name);
}
static private void setupSchema(VoltProjectBuilder project) throws IOException {
String literalSchema =
"CREATE TABLE T (\n"
+ " ti TINYINT,\n"
+ " si SMALLINT ,\n"
+ " int INTEGER NOT NULL ,\n"
+ " bi BIGINT ,\n"
+ " flt FLOAT ,\n"
+ " dec DECIMAL ,\n"
+ " ts TIMESTAMP ,\n"
+ " str VARCHAR(64),\n"
+ " bin VARBINARY(64),\n"
+ " pt GEOGRAPHY_POINT,\n"
+ " pol GEOGRAPHY \n"
+ ");\n"
;
project.addLiteralSchema(literalSchema);
project.setUseDDLSchema(true);
}
public void testTypeConversion() throws IOException, ProcCallException {
Client client = getClient();
client.callProcedure("T.Insert", 1, 1, 1, 1, 1, new BigDecimal(1),
"2012-12-01", "hi", "10", null, null);
// test different cases of type conversion that are allowed
client.callProcedure("ProcToTestTypeConversion",
ProcToTestTypeConversion.TestAllAllowedTypeConv,
0, 0);
// Use the conversion matrix to test type conversion cases that are allowed and blocked.
// This uses non-array argument list as supplied arguments for params (except varbinary
// that is byte[])
VoltType typeToTest = VoltType.INVALID;
String errorMsg = null;
for (int fromType = 0; fromType < m_tableColTypeVal.length; ++fromType) {
boolean[] supportedFromType = m_typeConversionMatrix[fromType];
typeToTest = m_tableColTypeVal[fromType];
for (int toType = 0; toType < m_tableColTypeVal.length; ++toType) {
if (supportedFromType[toType]) {
// type conversion feasible
client.callProcedure("ProcToTestTypeConversion",
ProcToTestTypeConversion.TestTypeConvWithInsertProc,
toType, typeToTest.getValue());
}
else {
String typeTriedByInsert = m_javaTypeNamePatternForInsertTest[fromType];
// type conversion not allowed
if (typeToTest == VoltType.TIMESTAMP &&
m_tableColTypeVal[toType] == VoltType.DECIMAL) {
// Conversion from Timestamp -> decimal is allowed in voltqueue
// sql as the Timestamp -> decimal is supported in comparison
// expression for select and insert statement with select statement with
// where predicate. So for update queries this works and hence is allowed
// in voltqueue sql. But in case of insert this conversion is not allowed
// as the conversion is flagged in EE with different error message So
// test for that
errorMsg = "Type "+ typeToTest.getName() +" can't be cast as "
+ m_tableColTypeVal[toType].getName();
}
else {
errorMsg = "Incompatible parameter type: can not convert type '"
+ typeTriedByInsert +
"' to '" + m_tableColTypeVal[toType].getName() +
"' for arg " + toType + " for SQL stmt";
}
verifyProcFails(client, errorMsg, "ProcToTestTypeConversion",
ProcToTestTypeConversion.TestTypeConvWithInsertProc,
toType, typeToTest.getValue());
}
if (typeToTest == VoltType.TINYINT &&
m_tableColTypeVal[fromType] == VoltType.TINYINT) {
continue;
}
// Test that array arguments are not typically compatible
// with parameters passed into column comparisons.
String typeTriedByCompare = m_javaTypeNamePatternForInListTest[fromType];
errorMsg = "Incompatible parameter type: can not convert type '"
+ typeTriedByCompare +
"' to '" + m_tableColTypeVal[toType].getName() +
"' for arg 0 for SQL stmt";
verifyProcFails(client, errorMsg, "ProcToTestTypeConversion",
ProcToTestTypeConversion.TestFailingArrayTypeCompare,
toType, typeToTest.getValue());
}
}
// Test cases an array argument is supplied to a select statement with
// an IN ? predicate to test the conversions allowed in comparison of in
// list arguments,
// Purposely starting fromType at index 1 instead of 0 because:
// Interpretation of byte[] as a list of tiny int is prevented
// by an overriding interpretation of byte[] as a VARBINARY value.
// So, it can not be supported,
// unless/until we find a way around that interpretation.
for (int fromType = 1; fromType < m_tableColTypeVal.length; ++fromType) {
boolean[] supportedForFromType = m_typeConversionMatrixInList[fromType];
String typeTriedByInListQuery = m_javaTypeNamePatternForInListTest[fromType];
typeToTest = m_tableColTypeVal[fromType];
for (int toType = 0; toType < m_tableColTypeVal.length; ++toType) {
VoltType inListType = m_tableColInListTypeVal[toType];
if (supportedForFromType[toType]) {
// type conversion feasible
client.callProcedure("ProcToTestTypeConversion",
ProcToTestTypeConversion.TestTypesInList,
toType, typeToTest.getValue());
if (inListType == null) {
// Some column types (non-integer, non-string) do not support
// IN LIST matching. these should be caught in the statement
// compiler as tested in a statement compiler test, not here.
continue;
}
}
else {
if (inListType == null) {
// Some column types (non-integer, non-string) do not support
// IN LIST matching. these should be caught in the statement
// compiler as tested in a statement compiler test, not here.
continue;
}
errorMsg = "Incompatible parameter type: can not convert type '"
+ typeTriedByInListQuery +
"' to '" + inListType.getName() +
"' for arg 0 for SQL stmt";
verifyProcFails(client, errorMsg, "ProcToTestTypeConversion",
ProcToTestTypeConversion.TestTypesInList,
toType, typeToTest.getValue());
}
// Test that non-array arguments are not allowed for IN LIST params.
String typeExpectedToFailInListQuery =
m_javaTypeNamePatternForInListFailureTest[fromType];
if (typeExpectedToFailInListQuery.endsWith("]") &&
inListType == VoltType.INLIST_OF_BIGINT) {
errorMsg = "rhs of IN expression is of a non-list type varbinary";
}
else {
errorMsg = "Incompatible parameter type: can not convert type '"
+ typeExpectedToFailInListQuery +
"' to '" + inListType.getName() +
"' for arg 0 for SQL stmt";
}
verifyProcFails(client, errorMsg, "ProcToTestTypeConversion",
ProcToTestTypeConversion.TestFailingTypesInList,
toType, typeToTest.getValue());
}
}
}
static public junit.framework.Test suite() {
VoltServerConfig config = null;
MultiConfigSuiteBuilder builder =
new MultiConfigSuiteBuilder(TestTypeConversionSuite.class);
boolean success = false;
try {
VoltProjectBuilder project = new VoltProjectBuilder();
config = new LocalCluster("geography-value-onesite.jar", 1, 1, 0, BackendTarget.NATIVE_EE_JNI);
setupSchema(project);
project.addProcedures(ProcToTestTypeConversion.class);
success = config.compile(project);
}
catch (IOException excp) {
fail();
}
assertTrue(success);
builder.addServerConfig(config);
return builder;
}
}