/* 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 java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import org.voltdb.BackendTarget;
import org.voltdb.VoltTable;
import org.voltdb.client.Client;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ProcCallException;
import org.voltdb.compiler.VoltProjectBuilder;
public class TestDecimalRoundingSuite extends RegressionSuite {
//
// JUnit / RegressionSuite boilerplate
//
public TestDecimalRoundingSuite(String name) {
super(name);
}
private void validateInsertStmt(boolean expectSuccess, String insertStmt, BigDecimal... expectedValues) throws Exception {
Client client = getClient();
boolean success;
try {
validateTableOfLongs(client, insertStmt, new long[][]{{1}});
success = true;
} catch (Exception ex) {
success = false;
}
assertEquals(getRoundingString("Insert Statement Failure"), success, expectSuccess);
if (!success) {
return;
}
validateTableOfDecimal(client, "select * from decimaltable;", new BigDecimal[][] {expectedValues});
if (success) {
validateTableOfLongs(client, "delete from decimaltable;", new long[][] {{1}});
}
}
public void testDecimalScaleInsertion() throws Exception {
Boolean roundIsEnabled = Boolean.valueOf(m_defaultRoundingEnablement);
RoundingMode roundMode = RoundingMode.valueOf(m_defaultRoundingMode);
assert(m_config instanceof LocalCluster);
LocalCluster localCluster = (LocalCluster)m_config;
Map<String, String> props = localCluster.getAdditionalProcessEnv();
if (props != null) {
roundIsEnabled = Boolean.valueOf(props.containsKey(m_roundingEnabledProperty) ? props.get(m_roundingEnabledProperty) : "true");
roundMode = RoundingMode.valueOf(props.containsKey(m_roundingModeProperty) ? props.get(m_roundingModeProperty) : "HALF_UP");
System.out.printf("Rounding is %senabled, mode is %s\n",
roundIsEnabled ? "" : "not ",
roundMode.toString());
} else {
System.out.printf("Default rounding (%s), Default Rounding mode (%s).\n",
roundIsEnabled.toString(), roundMode.toString());
}
doTestDecimalScaleInsertion(roundIsEnabled, roundMode);
}
private void doTestDecimalScaleInsertion(boolean roundingEnabled,
RoundingMode mode) throws Exception {
ClientConfig.setRoundingConfig(roundingEnabled, mode);
// Sanity check. See if we can insert a vanilla value.
validateInsertStmt(true,
"insert into decimaltable values 0.9;",
roundDecimalValue("0.900000000000",
roundingEnabled,
mode));
// See if we can insert an overscale number, and that we
// round appropriately.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values 0.999999999999999;",
roundDecimalValue("0.999999999999999",
roundingEnabled,
mode));
// Do the same as the last time, but make the last digit equal to 5.
// This should round up.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values 0.999999999999500;",
roundDecimalValue("0.999999999999500", roundingEnabled, mode));
// Do the same as the last time, but make the last digit equal to 4.
// This should round down.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values 0.9999999999994000;",
roundDecimalValue("0.9999999999994000",
roundingEnabled,
mode));
// Rounding gives the an extra digit of precision. Make sure
// that we don't take it from the scale.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values 9.9999999999999999;",
roundDecimalValue("9.9999999999999999",
roundingEnabled,
mode));
// Rounding here does *not* give an extra digit of precision. Make sure
// that we still get the expected scale.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values 9.4999999999999999;",
roundDecimalValue("9.4999999999999999",
roundingEnabled,
mode));
//
// Test negative numbers.
//
// Rounding gives the an extra digit of precision. Make sure
// that we don't take it from the scale.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values -9.9999999999999999;",
roundDecimalValue("-9.9999999999999999", roundingEnabled, mode));
// Rounding here does *not* give an extra digit of precision. Make sure
// that we still get the expected scale.
validateInsertStmt(roundingEnabled,
"insert into decimaltable values -9.4999999999999999;",
roundDecimalValue("-9.4999999999999999", roundingEnabled, mode));
validateInsertStmt(true,
"insert into decimaltable values null;", (BigDecimal)null);
//
// For these tests we give both a stored procedure and the
// equivalent ad-hoc sql for an insertion and a query.
// We execute the stored procedure insertion and query statements
// and then the ad hoc procedure and query statements back
// to back. After each we execute the clean procedure. That
// is, we execute:
// callStoredInsertProcedure
// callStoredQueryProcerue
// test that the queried value is what we expect
// cleanup the table
// callAdHocInsertProcedure
// callAdHocQueryProcedure
// test that the queried value is what we expect
// cleanup the table.
// Insert overscale decimal. Round up.
validateDecimalInsertStmt(roundingEnabled,
"INSERT_DECIMAL", "insert into decimaltable values ?",
new BigDecimal("9.9999999999999999"),
"FETCH_DECIMAL", "select dec from decimaltable;",
roundDecimalValue("9.9999999999999999", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal with 5 in the 13th digit. Round up.
validateDecimalInsertStmt(roundingEnabled,
"INSERT_DECIMAL", "insert into decimaltable values ?",
new BigDecimal("9.9999999999995"),
"FETCH_DECIMAL", "select dec from decimaltable;",
roundDecimalValue("9.9999999999995", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal with 4 in the 13th digit. Round down.
validateDecimalInsertStmt(roundingEnabled,
"INSERT_DECIMAL", "insert into decimaltable values ?",
new BigDecimal("9.9999999999994"),
"FETCH_DECIMAL", "select dec from decimaltable;",
roundDecimalValue("9.9999999999994", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal with 3 in the 13th digit. Round down.
validateDecimalInsertStmt(roundingEnabled,
"INSERT_DECIMAL", "insert into decimaltable values ?",
new BigDecimal("9.9999999999993"),
"FETCH_DECIMAL", "select dec from decimaltable;",
roundDecimalValue("9.9999999999993", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal, then search for less then the rounded down value.
// Expect to find nothing.
validateDecimalInsertStmtAdHoc(roundingEnabled,
"insert into decimaltable values ?",
new BigDecimal("9.9999999999994"),
"select dec from decimaltable where dec < 9.999999999999;",
null,
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal, then search for equal to the rounded down value.
// Expect to find the rounded down row.
validateDecimalInsertStmtAdHoc(roundingEnabled,
"insert into decimaltable values ?",
new BigDecimal("9.9999999999994"),
"select dec from decimaltable where dec = 9.999999999999;",
roundDecimalValue("9.9999999999994", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal, then search for equal to the inserted value.
// Expect to find the rounded down row, because the 9.9...2 value in the
// predicate is rounded as well.
validateDecimalInsertStmtAdHoc(roundingEnabled,
"insert into decimaltable values ?",
new BigDecimal("9.9999999999992"),
"select dec from decimaltable where dec = 9.9999999999992;",
roundDecimalValue("9.9999999999992", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal, then search for less than the inserted value.
// Expect to find nothing, because we inserted the rounded down value
// and the predicate's right hand constant is rounded to the same value.
validateDecimalInsertStmtAdHoc(roundingEnabled,
"insert into decimaltable values ?",
new BigDecimal("9.9999999999992"),
"select dec from decimaltable where dec < 9.9999999999992;",
null,
"TRUNCATE TABLE DECIMALTABLE;");
// Insert overscale decimal, search for the rounded down quantity exactly.
// Expect to find the rounded down row.
validateDecimalInsertStmtAdHoc(roundingEnabled,
"insert into decimaltable values ?",
new BigDecimal("9.9999999999993"),
"select dec from decimaltable where dec = 9.999999999999;",
roundDecimalValue("9.9999999999993", roundingEnabled, mode),
"TRUNCATE TABLE DECIMALTABLE;");
// Insert some constants. Check that the rounded down inserted values
// are truncated in the same way that the constants are.
validateDecimalQuery(roundingEnabled,
"insert into decimaltable values 9.9999999999999999;",
"select dec from decimaltable where dec < 9.9999999999999999;",
"truncate table decimaltable;"
/* No answers expected */
);
// Insert some constants. Check that the rounded down inserted values
// are truncated in the same way that the constants are.
validateDecimalQuery(roundingEnabled,
"insert into decimaltable values 9.9999999999999999;",
"select dec from decimaltable where dec = 9.9999999999999999;",
"truncate table decimaltable;",
roundDecimalValue("9.9999999999999999", roundingEnabled, mode)
);
if (roundingEnabled) {
//
// Make sure adding the smallest possible value to the
// largest possible value causes an underflow.
//
Client client = getClient();
ClientResponse cr = client.callProcedure("@AdHoc", "insert into decimaltable values 99999999999999999999999999.999999999999;");
assertEquals(getRoundingString("Insert statement failure"), ClientResponse.SUCCESS, cr.getStatus());
verifyStmtFails(client,
"select dec+0.000000000001 from decimaltable;",
"Attempted to add 99999999999999999999999999.999999999999 with 0.000000000001 causing overflow/underflow");
cr = client.callProcedure("@AdHoc", "truncate table decimaltable;");
assertEquals(getRoundingString("Table Cleanup failure"), ClientResponse.SUCCESS, cr.getStatus());
//
// Try it again with negative numbers.
//
cr = client.callProcedure("@AdHoc", "insert into decimaltable values -99999999999999999999999999.999999999999;");
assertEquals(getRoundingString("insert statement failure"), ClientResponse.SUCCESS, cr.getStatus());
verifyStmtFails(client,
"select dec-0.000000000001 from decimaltable;",
"Attempted to subtract 0.000000000001 from -99999999999999999999999999.999999999999 causing overflow/underflow");
cr = client.callProcedure("@AdHoc", "truncate table decimaltable;");
assertEquals(getRoundingString("Table Cleanup failure"), ClientResponse.SUCCESS, cr.getStatus());
//
// Try it with unrepresentable numbers.
//
String positiveTest = "insert into decimaltable values 99999999999999999999999999.9999999999995;";
String positiveTestMsg = "Unexpected Ad Hoc Planning Error: java.lang.RuntimeException: " +
"Error compiling query: java.lang.RuntimeException: " +
"Decimal 100000000000000000000000000.000000000000 has more than 38 digits of precision.";
String negativeTest = "insert into decimaltable values -99999999999999999999999999.9999999999995;";
String negativeTestMsg = "Unexpected Ad Hoc Planning Error: java.lang.RuntimeException: " +
"Error compiling query: java.lang.RuntimeException: " +
"Decimal -100000000000000000000000000.000000000000 has more than 38 digits of precision.";
switch (mode) {
case HALF_DOWN:
case DOWN:
// Rounding DOWN always rounds towards zero. So, we always get a
// representable number.
cr = client.callProcedure("@AdHoc", positiveTest);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
cr = client.callProcedure("@AdHoc", negativeTest);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
break;
case FLOOR:
// Floor rounds to the next lowest fixed point number. So,
// we never get positive overflow, but we may get negative overflow.
cr = client.callProcedure("@AdHoc", positiveTest);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
verifyStmtFails(client, negativeTest, negativeTestMsg);
break;
case CEILING:
// Ceiling rounds to the next highest representable number. So
// we never get negative overflow, but we may get positive
// overflow.
cr = client.callProcedure("@AdHoc", negativeTest);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
verifyStmtFails(client, positiveTest, positiveTestMsg);
break;
case HALF_UP:
case UP:
verifyStmtFails(client, positiveTest, positiveTestMsg);
verifyStmtFails(client, negativeTest, negativeTestMsg);
break;
default:
// Missed Case. We only test six of the eight possible
// rounding modes. This is here in case the test is expanded.
fail("Missed Rounding Case");
}
}
}
private void validateDecimalInsertStmt(boolean expectSuccess,
String storedInsProcName,
String adHocInsSQL,
BigDecimal parameter,
String storedProcQueryName,
String adHocQuerySQL,
BigDecimal expected,
String cleanup) throws Exception {
validateDecimalInsertStmtProcedure(expectSuccess, storedInsProcName, parameter, storedProcQueryName, expected, cleanup);
validateDecimalInsertStmtAdHoc(expectSuccess, adHocInsSQL, parameter, adHocQuerySQL, expected, cleanup);
}
private void validateDecimalInsertStmtAdHoc(boolean expectSuccess,
String insertStmt,
BigDecimal insertValue,
String fetchStmt,
BigDecimal expected,
String cleanupStmt) throws Exception {
Client client = getClient();
ClientResponse cr = null;
boolean success;
try {
cr = client.callProcedure("@AdHoc", insertStmt, insertValue);
success = true;
} catch (Exception ex) {
success = false;
}
assertEquals(getRoundingString("Insert statement Invocation Failure"), expectSuccess, success);
if (!success) {
return;
}
assertEquals(getRoundingString("Insert Statement Failure."), ClientResponse.SUCCESS, cr.getStatus());
cr = client.callProcedure("@AdHoc", fetchStmt);
assertEquals(getRoundingString("Fetch Data Failure"), ClientResponse.SUCCESS, cr.getStatus());
VoltTable[] tbls = cr.getResults();
assertEquals(getRoundingString("Volt Table Size Failure"), 1, tbls.length);
int idx = 0;
VoltTable tbl = tbls[0];
while (tbl.advanceRow()) {
BigDecimal actual = tbl.getDecimalAsBigDecimal(0);
assertNotSame(getRoundingString("Unexpected null table."), null, expected);
assertEquals(getRoundingString("Decimal Scale Failure"), expected, actual);
}
// A Null expected implies no results are expected.
if (expected == null) {
assertEquals(getRoundingString("Null expected:"), 0, idx);
}
cr = client.callProcedure("@AdHoc", cleanupStmt);
assertEquals(getRoundingString("Cleanup Statement Failure"), ClientResponse.SUCCESS, cr.getStatus());
}
private void validateDecimalInsertStmtProcedure(boolean expectSuccess,
String insertProcName,
BigDecimal insertValue,
String fetchProcName,
BigDecimal expected,
String cleanupProcedure) throws Exception {
Client client = getClient();
boolean success;
ClientResponse cr = null;
try {
cr = client.callProcedure(insertProcName, insertValue);
success = true;
} catch (Exception ex) {
success = false;
}
assertEquals(getRoundingString("Insert Statement Compilation Failure"), expectSuccess, success);
if (!success) {
return;
}
assertEquals(getRoundingString("Insert Statement Execution Failure"), ClientResponse.SUCCESS, cr.getStatus());
cr = client.callProcedure(fetchProcName);
assertEquals(getRoundingString("Fetch Data Failure"), ClientResponse.SUCCESS, cr.getStatus());
VoltTable[] tbls = cr.getResults();
assertEquals(getRoundingString("Number of results incorrect."), 1, tbls.length);
VoltTable tbl = tbls[0];
int idx = 0;
while (tbl.advanceRow()) {
BigDecimal actual = tbl.getDecimalAsBigDecimal(idx);
assertNotSame(getRoundingString("Null Table Failure"), null, expected);
assertEquals(getRoundingString("Decimal scale failure"), expected, actual);
}
if (expected == null) {
assertEquals(getRoundingString("Empty Results Expected."), 0, idx);
}
cr = client.callProcedure("@AdHoc", cleanupProcedure);
assertEquals(getRoundingString(null), ClientResponse.SUCCESS, cr.getStatus());
}
private void validateDecimalQuery(boolean expectSuccess,
String insertStmt,
String fetchStmt,
String cleanupStmt,
BigDecimal... expected) throws Exception {
Client client = getClient();
boolean success;
ClientResponse cr = null;
try {
cr = client.callProcedure("@AdHoc", insertStmt);
success = true;
} catch (Exception ex) {
success = false;
}
assertEquals(getRoundingString(null), expectSuccess, success);
if (!success) {
return;
}
assertEquals(getRoundingString(null), ClientResponse.SUCCESS, cr.getStatus());
cr = client.callProcedure("@AdHoc", fetchStmt);
assertEquals(getRoundingString(null), ClientResponse.SUCCESS, cr.getStatus());
VoltTable[] resultTable = cr.getResults();
int idx = 0;
VoltTable tbl = resultTable[0];
while (tbl.advanceRow()) {
BigDecimal actual = tbl.getDecimalAsBigDecimal(0);
assertTrue(idx < expected.length);
assertEquals(getRoundingString(null), expected[idx], actual);
idx += 1;
}
assertEquals(getRoundingString(null), idx, expected.length);
cr = client.callProcedure("@AdHoc", cleanupStmt);
assertEquals(getRoundingString("Cleanup statement failure"), ClientResponse.SUCCESS, cr.getStatus());
}
public void testEEDecimalScale() throws Exception {
Boolean roundIsEnabled = Boolean.valueOf(m_defaultRoundingEnablement);
RoundingMode roundMode = RoundingMode.valueOf(m_defaultRoundingMode);
assert(m_config instanceof LocalCluster);
LocalCluster localCluster = (LocalCluster)m_config;
Map<String, String> props = localCluster.getAdditionalProcessEnv();
if (props != null) {
roundIsEnabled = Boolean.valueOf(props.containsKey(m_roundingEnabledProperty) ? props.get(m_roundingEnabledProperty) : "true");
roundMode = RoundingMode.valueOf(props.containsKey(m_roundingModeProperty) ? props.get(m_roundingModeProperty) : "HALF_UP");
}
doTestEEDecimalScale(roundIsEnabled, roundMode);
}
private void doTestEEDecimalScale(boolean roundEnabled, RoundingMode roundMode) throws Exception {
// We currently only support one rounding mode in the EE, and
// we always round.
if (roundEnabled && roundMode == RoundingMode.HALF_UP) {
Client client = getClient();
ClientResponse cr;
String[] values = new String[] {
// Don't round.
"0.8999999999994",
// Do round.
"0.8999999999995",
// Do round to the left of the dot.
"0.9999999999995",
// Do round to the left of the dot with non-zero integer part.
"1.9999999999995",
// Do round to the left of the dot with more digits.
"99.9999999999995",
// Do round to the left of the dot with no more digits.
"98.9999999999995",
// Don't round, but the result is the largest
// representable decimal value.
"99999999999999999999999999.9999999999994999999",
// The following cases replicate the cases above,
// but with a sign.
"-0.8999999999994",
"-0.8999999999995",
"-0.9999999999995",
"-1.9999999999995",
"-99.9999999999995",
"-98.9999999999995",
"-99999999999999999999999999.9999999999994999999"
};
String[] badValues = new String[] {
// Too many integer digits.
"999999999999999999999999999999.0",
// Round to an unrepresentable value.
"99999999999999999999999999.9999999999995999999",
// Round to an unrepresentable value.
"-99999999999999999999999999.9999999999995999999"
};
// Insert some data.
for (String val : values) {
cr = client.callProcedure("EEDecimal.Insert", val);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
}
// Insert some bad data.
// We will find this is bad later on.
for (int idx = 0; idx < badValues.length; idx += 1) {
String val = badValues[idx];
cr = client.callProcedure("EEBadDecimal.Insert", idx, val);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
}
// Query the data. Just get them all for now.
cr = client.callProcedure("EEDecimalFetch");
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
VoltTable tbl = cr.getResults()[0];
while (tbl.advanceRow()) {
String expectedStr = tbl.getString(0);
BigDecimal rounded = tbl.getDecimalAsBigDecimal(1);
BigDecimal expected = roundDecimalValue(expectedStr, roundEnabled, roundMode);
assertEquals(expected, rounded);
}
// Query the data for bad values. Get them one at a time,
// because they are all bad in their own way.
for (int idx = 0; idx < badValues.length; idx += 1) {
try {
cr = client.callProcedure("EEBadDecimalFetch", idx);
fail(String.format("Unexpected success, case %d, decimal string \"%s\"",
idx, badValues[idx]));
} catch (ProcCallException ex) {
assertTrue(true);
}
}
} else {
assertTrue(true);
}
}
/**
* Test rounding on columns with decimal default. HSQLDB does not give us the
* right value for decimal strings, so until ENG-8557 is
* @param roundEnabled
* @param roundMode
* @throws Exception
*/
public final void notestDecimalDefault(boolean roundEnabled, RoundingMode roundMode) throws Exception {
validateDecimalDefault("pRoundDecimalDownNC", false, roundEnabled, roundMode, "0.8999999999994");
validateDecimalDefault("pRoundDecimalUpNC", false, roundEnabled, roundMode, "0.8999999999995");
validateDecimalDefault("pRoundDecimalDownC", false, roundEnabled, roundMode, "0.9999999999994");
validateDecimalDefault("pRoundDecimalUpC", false, roundEnabled, roundMode, "0.9999999999995");
validateDecimalDefault("pRoundDecimalDownC2", false, roundEnabled, roundMode, "99.9999999999994");
validateDecimalDefault("pRoundDecimalUpC2", false, roundEnabled, roundMode, "99.9999999999995");
validateDecimalDefault("pRoundDecimalMax", false, roundEnabled, roundMode, "99999999999999999999999999.9999999999994");
validateDecimalDefault("pRoundDecimalNotRep", true, roundEnabled, roundMode, "99999999999999999999999999.9999999999995");
validateDecimalDefault("nRoundDecimalDownNC", false, roundEnabled, roundMode, "-0.8999999999994");
validateDecimalDefault("nRoundDecimalUpNC", false, roundEnabled, roundMode, "-0.8999999999995");
validateDecimalDefault("nRoundDecimalDownC", false, roundEnabled, roundMode, "-0.9999999999994");
validateDecimalDefault("nRoundDecimalUpC", false, roundEnabled, roundMode, "-0.9999999999995");
validateDecimalDefault("nRoundDecimalDownC2", false, roundEnabled, roundMode, "-99.9999999999994");
validateDecimalDefault("nRoundDecimalUpC2", false, roundEnabled, roundMode, "-99.9999999999995");
validateDecimalDefault("nRoundDecimalMax", false, roundEnabled, roundMode, "-99999999999999999999999999.9999999999994");
validateDecimalDefault("nRoundDecimalNotRep", true, roundEnabled, roundMode, "-99999999999999999999999999.9999999999995");
}
private final void validateDecimalDefault(String tableName,
boolean shouldFail,
boolean roundEnabled,
RoundingMode roundMode,
String value) throws Exception {
Client client = getClient();
ClientResponse cr;
boolean sawFail = false;
VoltTable tbl = null;
BigDecimal found = null;
try {
cr = client.callProcedure("@AdHoc", String.format("insert into %s (id) values ?;", tableName), 100);
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
sawFail = false;
cr = client.callProcedure("@AdHoc", String.format("select (dec) from %s;", tableName));
assertEquals(ClientResponse.SUCCESS, cr.getStatus());
tbl = cr.getResults()[0];
assertTrue(tbl.advanceRow());
found = tbl.getDecimalAsBigDecimal(0);
} catch (ProcCallException ex) {
sawFail = true;
}
assertEquals(shouldFail ? "Expected a failure here" : "Unexpected failure here",
shouldFail, sawFail);
if (shouldFail) {
return;
}
BigDecimal expected = roundDecimalValue(value, roundEnabled, roundMode);
assertEquals(String.format("Default decimal value failed: rounding is %s, mode is %s",
roundEnabled ? "enabled" : "not enabled",
roundMode),
expected,
found);
}
private final static Map<String, String> makePropertiesMap(String... entries) {
assert(entries.length % 2 == 0);
Map<String, String> answer = new HashMap<String, String>();
for (int idx = 0; idx < entries.length; idx += 2) {
answer.put(entries[idx], entries[idx+1]);
}
return answer;
}
private static void addConfig(int idx, MultiConfigSuiteBuilder builder, VoltProjectBuilder project, Map<String, String> properties) {
LocalCluster config = null;
config = new LocalCluster("sqlinsert-onesite.jar", 2, 1, 0, BackendTarget.NATIVE_EE_JNI, properties);
String renabled = (properties != null) ? properties.get(m_roundingEnabledProperty) : "defEnabled";
String rmode = (properties != null) ? properties.get(m_roundingModeProperty) : "defEnabled";
if (renabled == null) {
renabled = "defEnabled";
}
if (rmode == null) {
rmode = "defMode";
}
config.setPrefix(renabled + "-" + rmode);
config.setHasLocalServer(false);
boolean success = config.compile(project);
assert(success);
builder.addServerConfig(config);
}
static public junit.framework.Test suite() {
MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestDecimalRoundingSuite.class);
VoltProjectBuilder project = new VoltProjectBuilder();
final String literalSchema =
"CREATE TABLE P1 ( id integer );" +
"CREATE TABLE DECIMALTABLE ( " +
"dec decimal" +
");" +
"CREATE PROCEDURE INSERT_DECIMAL AS " +
"INSERT INTO DECIMALTABLE VALUES ?;" +
"CREATE PROCEDURE FETCH_DECIMAL AS " +
"SELECT DEC FROM DECIMALTABLE;" +
"CREATE PROCEDURE TRUNCATE_DECIMAL AS " +
"TRUNCATE TABLE DECIMALTABLE;" +
"create table EEDecimal (" +
" valueIn varChar(128)" +
");" +
"create procedure EEDecimalFetch as " +
"select valueIn, cast(valueIn as decimal) from EEDecimal;" +
"create table EEBadDecimal (" +
" id integer primary key not null," +
" valueIn varChar(128)" +
");" +
"create procedure EEBadDecimalFetch as " +
"select valueIn, cast(valueIn as decimal) from EEBadDecimal where id = ?;" +
"create table pRoundDecimalDownNC ( id integer, dec decimal default '0.8999999999994' );" +
"create table pRoundDecimalUpNC ( id integer, dec decimal default '0.8999999999995' );" +
"create table pRoundDecimalDownC ( id integer, dec decimal default '0.9999999999994' );" +
"create table pRoundDecimalUpC ( id integer, dec decimal default '0.9999999999995' );" +
"create table pRoundDecimalDownC2 ( id integer, dec decimal default '99.9999999999994' );" +
"create table pRoundDecimalUpC2 ( id integer, dec decimal default '99.9999999999995' );" +
"create table pRoundDecimalMax ( id integer, dec decimal default '99999999999999999999999999.9999999999994' );" +
"create table pRoundDecimalNotRep ( id integer, dec decimal default '99999999999999999999999999.9999999999995' );" +
"create table nRoundDecimalDownNC ( id integer, dec decimal default '-0.8999999999994' );" +
"create table nRoundDecimalUpNC ( id integer, dec decimal default '-0.8999999999995' );" +
"create table nRoundDecimalDownC ( id integer, dec decimal default '-0.9999999999994' );" +
"create table nRoundDecimalUpC ( id integer, dec decimal default '-0.9999999999995' );" +
"create table nRoundDecimalDownC2 ( id integer, dec decimal default '-99.9999999999994' );" +
"create table nRoundDecimalUpC2 ( id integer, dec decimal default '-99.9999999999995' );" +
"create table nRoundDecimalMax ( id integer, dec decimal default '-99999999999999999999999999.9999999999994' );" +
"create table nRoundDecimalNotRep ( id integer, dec decimal default '-99999999999999999999999999.9999999999995' );" +
""
;
try {
project.addLiteralSchema(literalSchema);
} catch (IOException e) {
assertFalse(true);
}
int idx = 0;
addConfig(idx++, builder, project, null);
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true"));
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true", m_roundingModeProperty, "HALF_UP"));
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true", m_roundingModeProperty, "HALF_DOWN"));
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true", m_roundingModeProperty, "CEILING"));
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true", m_roundingModeProperty, "FLOOR"));
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true", m_roundingModeProperty, "UP"));
addConfig(idx++, builder, project, makePropertiesMap(m_roundingEnabledProperty, "true", m_roundingModeProperty, "DOWN"));
return builder;
}
}