/* 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.compiler;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import junit.framework.TestCase;
import org.voltdb.VoltDB.Configuration;
public class TestVoltCompilerErrorMsgs extends TestCase {
private void statementTest(String feature, boolean expectError, boolean testStmtIsDdl, String... statements)
throws Exception
{
String simpleSchema =
"create table blah (" +
"ival bigint default 0 not null, " +
"sval varchar(255) not null," +
"tinyval tinyint," +
"floatval float," +
"decval decimal" +
");" +
"create table indexed_blah (" +
"ival bigint default 0 not null, " +
"sval varchar(255) not null, " +
"PRIMARY KEY(ival)" +
");" +
"create table partitioned_blah (" +
"ival bigint default 0 not null, " +
"sval varchar(255) not null," +
"tinyval tinyint," +
"floatval float," +
"decval decimal" +
");" +
"partition table partitioned_blah on column sval;" +
"";
VoltProjectBuilder builder = new VoltProjectBuilder();
ByteArrayOutputStream capturer = new ByteArrayOutputStream();
PrintStream capturing = new PrintStream(capturer);
builder.setCompilerDebugPrintStream(capturing);
builder.addLiteralSchema(simpleSchema);
for (String statement : statements) {
if (testStmtIsDdl) {
builder.addLiteralSchema(statement);
} else {
builder.addStmtProcedure(feature, statement);
}
}
boolean success = builder.compile(Configuration.getPathToCatalogForTest("errors.jar"));
String captured = capturer.toString("UTF-8");
String[] lines = captured.split("\n");
if (expectError) {
assertFalse("Expected an error containing \"" + feature + "\", but compilation succeeded.", success);
String pattern = ".*[Ee]rror.*" + feature + ".*";
assertTrue("Expected an error matching pattern \"" + pattern + "\" in output \"" + captured + "\", but no matching line was found",
foundLineMatching(lines, pattern));
}
else {
assertTrue("Expected no errors, but compilation failed with this output: \"" + captured + "\".", success);
}
}
private boolean foundLineMatching(String[] lines, String pattern) {
for (String string : lines) {
if (string.matches(pattern)) {
return true;
}
}
return false;
}
void statementErrorTest(String feature, String... statements) throws Exception {
statementTest(feature, true, true, statements);
}
void statementNonErrorTest(String feature, String... statements) throws Exception {
statementTest(feature, false, false, statements);
}
void ddlErrorTest(String feature, String... statements) throws Exception {
statementTest(feature, true, true, statements);
}
void ddlNonErrorTest(String feature, String... statements) throws Exception {
statementTest(feature, false, true, statements);
}
public void testNoErrorOnParens() throws Exception {
statementNonErrorTest("PARENS", "(select ival from blah);");
}
public void testErrorOnVarchar0() throws Exception {
ddlErrorTest("out of range", "create table hassize0vc (vc varchar(0));");
}
public void testErrorOnVarbinary0() throws Exception {
ddlErrorTest("out of range", "create table hassize0vb (vb varbinary(0));");
}
public void testErrorOnSizedInteger() throws Exception {
// We do not support sized integers.
ddlErrorTest("unexpected token", "create table hassizedint (i integer(5));");
}
public void testLargeVarcharColumns() throws Exception {
// This is a regression test for ENG-10560.
// Tests for sizes of complete row
ddlErrorTest("Table T has a maximum row size of 2097156 but the maximum supported row size is 2097152",
"create table t (vc1 varchar(262144), vc2 varchar(262143));");
ddlErrorTest("Table T has a maximum row size of 2097153 but the maximum supported row size is 2097152",
"create table t (vc1 varchar(1048572 bytes), vc2 varchar(1048573 bytes));");
ddlNonErrorTest(null, "create table t (vc1 varchar(262143), vc2 varchar(262143));");
ddlNonErrorTest(null, "create table t (vc1 varchar(1048572 bytes), vc2 varchar(1048572 bytes));");
}
public void testErrorOnInsertIntoSelect() throws Exception {
// This should fail.
ddlErrorTest("statement manipulates data in a content non-deterministic way",
"create procedure MyInsert as insert into partitioned_blah (sval, ival) select sval, ival from blah where sval = ? limit 1;",
"partition procedure MyInsert on table partitioned_blah column sval;");
// limit with no order by implies non-deterministic content, so we won't compile the statement.
ddlErrorTest("statement manipulates data in a content non-deterministic way",
"create procedure MyInsert as insert into partitioned_blah (sval, ival) " +
"select sval, ival from blah where sval = ? limit 1;",
"partition procedure MyInsert on table partitioned_blah column sval;");
// Limit with an order by is okay.
ddlNonErrorTest("INSERT",
"create procedure MyInsert as insert into partitioned_blah (sval, ival) " +
"select sval, ival from blah where sval = ? order by 1, 2 limit 1;",
"partition procedure MyInsert on table partitioned_blah column sval;");
// if it's marked as single-partition, it's ok.
ddlNonErrorTest("INSERT",
"create procedure MyInsert as insert into partitioned_blah (sval) select sval from indexed_blah where sval = ?;",
"partition procedure MyInsert on table partitioned_blah column sval;");
// insert into replicated is fine for MP stored procedure
ddlNonErrorTest("INSERT",
"create procedure MyInsert as insert into blah (sval) select sval from indexed_blah where sval = ?;");
// ...but should fail for SP stored procedure
ddlErrorTest("Trying to write to replicated table 'BLAH' in a single-partition procedure.",
"create procedure MyInsert as insert into blah (sval) select sval from indexed_blah where sval = ?;",
"partition procedure MyInsert on table partitioned_blah column sval;");
// ...and insert into replicated table should fail if the select accesses any partitioned tables
ddlErrorTest("Subquery in INSERT INTO ... SELECT statement may not access partitioned data " +
"for insertion into replicated table BLAH.",
"create procedure MyInsert as insert into blah (sval) select sval from partitioned_blah where sval = ?;");
// UNION is still unsupported
ddlErrorTest("not supported for UNION or other set operations",
"create procedure MyInsert as insert into blah (sval) " +
"select sval from indexed_blah where sval = ? union select sval from indexed_blah where sval = ?;");
// query expression/target column degree mismatch
ddlErrorTest("number of target columns does not match that of query expression",
"create procedure MyInsert as insert into partitioned_blah (sval) select sval, sval || '!' from indexed_blah where sval = ?;",
"partition procedure MyInsert on table partitioned_blah column sval;");
// parameter in select list needs a cast.
ddlErrorTest("data type cast needed for parameter or null literal",
"create procedure insert_param_in_select_list as " +
"insert into partitioned_blah (ival, sval) " +
"select ival, ? from blah order by ival, sval;",
"partition procedure insert_param_in_select_list on table partitioned_blah column sval;");
// inserting into replicated table should fail
ddlErrorTest("Trying to write to replicated table 'BLAH' in a single-partition procedure.",
"create procedure insert_into_replicated_select as " +
"insert into blah select * from partitioned_blah;" +
"partition procedure insert_into_replicated_select on table partitioned_blah column sval;");
}
public void testErrorOnLimitPartitionRows() throws Exception {
ddlErrorTest("Table T1 has invalid DELETE statement for LIMIT PARTITION ROWS constraint: not a DELETE statement",
"create table t1 (i integer, "
+ "constraint row_limit limit partition rows 5 "
+ "execute (insert into t1 values(3)));");
ddlErrorTest("Table T1 has invalid DELETE statement for LIMIT PARTITION ROWS constraint: target of DELETE must be T1",
"create table t1 (i integer, "
+ "constraint row_limit limit partition rows 5 "
+ "execute (delete from partitioned_blah));");
ddlErrorTest("Table T1 has invalid DELETE statement for LIMIT PARTITION ROWS constraint: parse error: SQL Syntax error",
"create table t1 (i integer, "
+ "constraint row_limit limit partition rows 5 "
+ "execute (delete frm partitioned_blah));");
}
public void testHexLiterals() throws Exception {
// 0 digits is not valid to specify a number.
ddlErrorTest("invalid format for a constant bigint value",
"create procedure insHex as insert into blah (ival) values (x'');");
// The HSQL parser complains about an odd number of digits.
ddlErrorTest("malformed binary string",
"create procedure insHex as insert into blah (ival) values (x'0123456789abcdef0');");
// Too many digits for a BIGINT.
ddlErrorTest("invalid format for a constant bigint value",
"create procedure insHex as insert into blah (ival) values (x'0123456789abcdef01');");
// Check that we get range checks right when inserting into a field narrower
// than BIGINT.
ddlErrorTest("Constant value overflows/underflows TINYINT type.",
"create procedure insHex as insert into blah (tinyval) values (x'FF');");
ddlErrorTest("Constant value overflows/underflows TINYINT type.",
"create procedure insHex as insert into blah (tinyval) values (x'80');");
// This is -128, the null value.
ddlErrorTest("Constant value overflows/underflows TINYINT type.",
"create procedure insHex as insert into blah (tinyval) values (x'FfffFfffFfffFf80');");
// This is -129.
ddlErrorTest("Constant value overflows/underflows TINYINT type.",
"create procedure insHex as insert into blah (tinyval) values (x'FfffFfffFfffFf7f');");
// Hex constants not allowed to initialize DECIMAL or FLOAT.
ddlErrorTest("invalid format for a constant float value",
"create procedure insHex as insert into blah (floatval) values (x'80');");
ddlErrorTest("invalid format for a constant decimal value",
"create procedure insHex as insert into blah (decval) values (x'80');");
// In arithmetic or logical expressions, we catch malformed (for BIGINT) x-literals
// in HSQL.
ddlErrorTest("malformed numeric constant",
"create procedure selHex as select 30 + X'' from blah;");
// (too many hex digits)
ddlErrorTest("malformed numeric constant",
"create procedure selHex as select tinyval from blah where X'0000000000000000FF' < tinyval;");
}
public void testHexLiteralDefaultValues() throws Exception {
ddlErrorTest("malformed numeric constant",
"create table t (bi bigint default X'');");
ddlErrorTest("malformed numeric constant",
"create table t (bi bigint default X'FFFF0000FFFF0000FF');");
ddlErrorTest("numeric value out of range",
"create table t (ti tinyint default X'80');");
ddlErrorTest("numeric value out of range",
"create table t (ti tinyint default X'FF');");
// This does not fail, but it seems like it should fail with an
// out of range error. -128 is reserved for the TINYINT null value
// This is ENG-8148.
ddlNonErrorTest("create table t (ti tinyint default -X'80');");
ddlErrorTest("numeric value out of range",
"create table t (ti tinyint default -X'81');");
}
}