/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB L.L.C. * * 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; import java.math.BigDecimal; import java.util.Arrays; import junit.framework.TestCase; import org.voltdb.VoltTable.ColumnInfo; import org.voltdb.messaging.FastSerializableTestUtil; import org.voltdb.types.TimestampType; import org.voltdb.types.VoltDecimalHelper; public class TestVoltTable extends TestCase { private VoltTable LONG_FIVE; private VoltTable t; private VoltTable t2; @Override public void setUp() { LONG_FIVE = new VoltTable(new VoltTable.ColumnInfo("Test", VoltType.BIGINT)); LONG_FIVE.addRow(5L); t = new VoltTable(); t2 = new VoltTable(); } void addAllPrimitives(Class<?>[] permittedTypes) { Object[] primitives = { null, (byte) 0, (short) 1, (short) 2, 3, 4L, 5.01f, 6.02, "string", new byte[] { 'b', 'y', 't', 'e', 's' }, new TimestampType(99), new BigDecimal(7654321).setScale(VoltDecimalHelper.kDefaultScale), new Object(), }; for (Object o : primitives) { try { t.addRow(o); if (o != null && !contains(permittedTypes, o.getClass())) { fail(o.getClass() + " is not permitted but addRow succeeded"); } } catch (VoltTypeException e) { if (contains(permittedTypes, o.getClass())) { fail(o.getClass() + " is permitted by addRow failed"); } } } } // VoltTable initially sizes itself to fit exactly, so 2 longs should be sufficient to get // resizing. Use 16 to be on the safe side. private static final int LONGS_TO_RESIZE = 16; // return an VoltTable that needed to grow once. private VoltTable makeResizedTable() { VoltTable temp = new VoltTable(new ColumnInfo("Foo", VoltType.BIGINT)); for (int i = 0; i < LONGS_TO_RESIZE; ++i) { temp.addRow((long) i); } return temp; } private static boolean contains(Class<?>[] types, Class<?> type) { for (Class<?> c : types) { if (type == c) return true; } return false; } private boolean comparisonHelper(Object lhs, Object rhs, VoltType vt) { switch (vt) { case TINYINT: Byte b1 = (Byte)lhs; Byte b2 = (Byte)rhs; return b1.byteValue() == b2.byteValue(); case SMALLINT: Short s1 = (Short)lhs; Short s2 = (Short)rhs; return s1.shortValue() == s2.shortValue(); case INTEGER: Integer i1 = (Integer)lhs; Integer i2 = (Integer)rhs; return i1.intValue() == i2.intValue(); case BIGINT: Long l1 = (Long)lhs; Long l2 = (Long)rhs; return l1.longValue() == l2.longValue(); case FLOAT: Double d1 = (Double)lhs; Double d2 = (Double)rhs; return (d1.compareTo(d2) == 0); case STRING: if (lhs == null && rhs == null) return true; if (lhs == VoltType.NULL_STRING && rhs == null) return true; return ((String)lhs).equals(rhs); case TIMESTAMP: if (lhs == null && rhs == null) return true; if (lhs == VoltType.NULL_TIMESTAMP && rhs == null) return true; return ((TimestampType)lhs).equals(rhs); case DECIMAL: if (lhs == null && rhs == null) return true; if (lhs == VoltType.NULL_DECIMAL && rhs == null) return true; if (lhs == null || rhs == null) return false; return ((BigDecimal)lhs).equals(rhs); } return false; } public void testMakeFromScalar() { assertEquals(1, LONG_FIVE.getColumnCount()); assertEquals(1, LONG_FIVE.getRowCount()); assertEquals("Test", LONG_FIVE.getColumnName(0)); } public void testAsScalarLong() { assertEquals(5L, LONG_FIVE.asScalarLong()); } public void testAddColumnNullName() { try { t = new VoltTable(new ColumnInfo(null, VoltType.BIGINT)); fail("expected exception"); } catch (IllegalArgumentException e) {} } public void testLongSchemaOver1K() { // this test would crash before r531 StringBuilder columnname = new StringBuilder(); while (columnname.length() < 8192) { columnname.append("ryanlikestheyankees"); } t = new VoltTable(new ColumnInfo(columnname.toString(), VoltType.BIGINT)); assertTrue(t.getColumnName(0).compareTo(columnname.toString()) == 0); } public void testColumnIndexBounds() { try { LONG_FIVE.fetchRow(0).getLong(-1); fail("expected exception"); } catch (IndexOutOfBoundsException e) {} try { LONG_FIVE.fetchRow(0).getLong(1); fail("expected exception"); } catch (IndexOutOfBoundsException e) {} } public void testColumnTypeMismatch() { try { LONG_FIVE.fetchRow(0).getString(0); fail("expected exception"); } catch (IllegalArgumentException e) {} } public void testColumnByName() { t = new VoltTable(new ColumnInfo("foo", VoltType.STRING), new ColumnInfo("twofoo", VoltType.INTEGER)); t.addRow("bar", 5); assertEquals(0, t.getColumnIndex("foo")); assertEquals(1, t.getColumnIndex("twofoo")); assertEquals(t.getColumnName(0).equals("foo"), true); assertEquals(t.getColumnName(1).equals("twofoo"), true); assertEquals(t.getColumnName(1).compareTo("twofoo"), 0); System.out.println(t.toString()); VoltTableRow r = t.fetchRow(0); assertEquals("bar", r.getString("foo")); try { t.getColumnIndex("bar"); fail("expected exception"); } catch (IllegalArgumentException e) {} try { r.getString("bar"); fail("expected exception"); } catch (IllegalArgumentException e) {} } public void testAddRow() { t = new VoltTable(new ColumnInfo("foo", VoltType.BIGINT)); try { t.addRow(42L, 47L); fail("expected exception (1)"); } catch (IllegalArgumentException e) {} try { Object[] objs = new Object[] {new Long(50), new Long(51) }; t.addRow(objs); fail("expected exception (2)"); } catch (IllegalArgumentException e) {} } public void testResizedTable() { // Create a table big enough to require resizing t = makeResizedTable(); assertEquals(LONGS_TO_RESIZE, t.getRowCount()); for (int i = 0; i < LONGS_TO_RESIZE; ++i) { assertEquals(i, t.fetchRow(i).getLong(0)); } } @SuppressWarnings("deprecation") public void testEquals() { assertFalse(LONG_FIVE.equals(null)); assertFalse(LONG_FIVE.equals(new Object())); // Different column name t = new VoltTable(new VoltTable.ColumnInfo("Test2", VoltType.BIGINT)); t.addRow(5L); assertFalse(LONG_FIVE.equals(t)); // Different number of columns t = new VoltTable( new ColumnInfo("Test", VoltType.BIGINT), new ColumnInfo("Test2", VoltType.BIGINT) ); assertFalse(LONG_FIVE.equals(t)); t.addRow(5L, 10L); assertFalse(LONG_FIVE.equals(t)); // These are the same table t = new VoltTable( new ColumnInfo("Test", VoltType.BIGINT) ); t.addRow(5L); assertEquals(LONG_FIVE, t); // Test two tables with strings t = new VoltTable(new VoltTable.ColumnInfo("Foo", VoltType.STRING)); t.addRow("Bar"); VoltTable t2 = new VoltTable(new VoltTable.ColumnInfo("Foo", VoltType.STRING)); t2.addRow("Baz"); assertFalse(t.equals(t2)); t2 = new VoltTable(new VoltTable.ColumnInfo("Foo", VoltType.STRING)); t2.addRow("Bar"); assertEquals(t, t2); } @SuppressWarnings("deprecation") public void testEqualsDeserialized() { t = makeResizedTable(); t2 = FastSerializableTestUtil.roundTrip(t); boolean equal = t.equals(t2); assertTrue(equal); } public void testStrings() { t = new VoltTable(new ColumnInfo("", VoltType.STRING)); addAllPrimitives(new Class[]{ String.class, byte[].class }); t.addRow(""); assertEquals("string", t.fetchRow(1).getString(0)); t2 = FastSerializableTestUtil.roundTrip(t); assertEquals("", t2.getColumnName(0)); assertEquals(4, t2.getRowCount()); VoltTableRow r = t2.fetchRow(0); assertNull(r.getString(0)); assertTrue(r.wasNull()); assertEquals("string", t2.fetchRow(1).getString(0)); assertEquals("bytes", t2.fetchRow(2).getString(0)); assertEquals("", t2.fetchRow(3).getString(0)); t2.clearRowData(); assertTrue(t2.getRowCount() == 0); } public void testStringsAsBytes() { t = new VoltTable(new ColumnInfo("", VoltType.STRING)); t.addRow(new byte[0]); final byte[] FOO = new byte[]{'f', 'o', 'o'}; t.addRow(FOO); t2 = FastSerializableTestUtil.roundTrip(t); assertEquals(2, t2.getRowCount()); assertEquals("", t2.fetchRow(0).getString(0)); assertEquals(0, t2.fetchRow(0).getStringAsBytes(0).length); assertTrue(Arrays.equals(FOO, t2.fetchRow(1).getStringAsBytes(0))); assertEquals("foo", t2.fetchRow(1).getString(0)); t2.clearRowData(); assertTrue(t2.getRowCount() == 0); } public void testIntegers() { t = new VoltTable(new ColumnInfo("foo", VoltType.BIGINT)); addAllPrimitives(new Class[] { Long.class, Integer.class, Short.class, Byte.class, Double.class, Float.class } ); t2 = FastSerializableTestUtil.roundTrip(t); assertEquals(8, t2.getRowCount()); assertEquals(0, t2.fetchRow(1).getLong(0)); VoltTableRow r = t2.fetchRow(0); assertEquals(VoltType.NULL_BIGINT, r.getLong(0)); assertTrue(r.wasNull()); t2.clearRowData(); assertTrue(t2.getRowCount() == 0); } public void testExactTypes() { VoltTable basecase = new VoltTable(new ColumnInfo("foo", VoltType.DECIMAL)); basecase.addRow(new BigDecimal(7654321).setScale(VoltDecimalHelper.kDefaultScale)); VoltTableRow basecaserow = basecase.fetchRow(0); BigDecimal bd = basecaserow.getDecimalAsBigDecimal(0); assertEquals(bd, new BigDecimal(7654321).setScale(VoltDecimalHelper.kDefaultScale)); t = new VoltTable(new ColumnInfo("foo", VoltType.DECIMAL)); addAllPrimitives(new Class[] { BigDecimal.class }); t2 = FastSerializableTestUtil.roundTrip(t); assertEquals(2, t2.getRowCount()); // row 0 contains NULL VoltTableRow r = t2.fetchRow(0); r.getDecimalAsBigDecimal(0); assertTrue(r.wasNull()); // row 1 contains a known value r = t2.fetchRow(1); assertTrue(new BigDecimal(7654321).setScale(VoltDecimalHelper.kDefaultScale). equals(r.getDecimalAsBigDecimal(0))); t2.clearRowData(); assertTrue(t2.getRowCount() == 0); } public void testFloats() { t = new VoltTable(new ColumnInfo("foo", VoltType.FLOAT)); addAllPrimitives(new Class[]{ Long.class, Integer.class, Short.class, Byte.class, Double.class, Float.class }); t2 = FastSerializableTestUtil.roundTrip(t); assertEquals(8, t2.getRowCount()); VoltTableRow r = t2.fetchRow(0); assertEquals(VoltType.NULL_FLOAT, r.getDouble(0)); assertTrue(r.wasNull()); assertEquals(0.0, t2.fetchRow(1).getDouble(0), .000001); assertEquals(1.0, t2.fetchRow(2).getDouble(0), .000001); assertEquals(2.0, t2.fetchRow(3).getDouble(0), .000001); assertEquals(3.0, t2.fetchRow(4).getDouble(0), .000001); assertEquals(4.0, t2.fetchRow(5).getDouble(0), .000001); assertEquals(5.01, t2.fetchRow(6).getDouble(0), .000001); assertEquals(6.02, t2.fetchRow(7).getDouble(0), .000001); t2.clearRowData(); assertTrue(t2.getRowCount() == 0); } // At least check that NULL_VALUEs of one type get interpreted as NULL // if we attempt to put them into a column of a different type public void testNulls() { VoltType[] types = {VoltType.TINYINT, VoltType.SMALLINT, VoltType.INTEGER, VoltType.BIGINT, VoltType.FLOAT, VoltType.DECIMAL, VoltType.TIMESTAMP, VoltType.STRING}; for (int i = 0; i < types.length; ++i) { for (int j = 0; j < types.length; ++j) { VoltTable table = new VoltTable(new ColumnInfo("test_table", types[i])); table.addRow(types[j].getNullValue()); VoltTableRow row = table.fetchRow(0); row.get(0, types[i]); assertTrue("Value wasn't null", row.wasNull()); } } } public void testTruncatingCasts() { VoltType[] test_types = {VoltType.TINYINT, VoltType.SMALLINT, VoltType.INTEGER}; Object[][] test_vals = {{(long) Byte.MAX_VALUE, ((long) Byte.MAX_VALUE) + 1, ((long) Byte.MIN_VALUE) - 1}, {(long) Short.MAX_VALUE, ((long) Short.MAX_VALUE) + 1, ((long) Short.MIN_VALUE) - 1}, {(long) Integer.MAX_VALUE, ((long) Integer.MAX_VALUE) + 1, ((long) Integer.MIN_VALUE) - 1}}; for (int i = 0; i < test_types.length; ++i) { t = new VoltTable(new ColumnInfo("test_table", test_types[i])); t.addRow(test_vals[i][0]); boolean caught = false; try { t.addRow(test_vals[i][1]); } catch (VoltTypeException e) { caught = true; } assertTrue("Failed on: " + test_types[i].toString(), caught); caught = false; try { t.addRow(test_vals[i][2]); } catch (VoltTypeException e) { caught = true; } assertTrue("Failed on: " + test_types[i].toString(), caught); } } public void testTimestamps() { t = new VoltTable(new ColumnInfo("foo", VoltType.TIMESTAMP)); addAllPrimitives(new Class[]{Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, TimestampType.class}); t2 = FastSerializableTestUtil.roundTrip(t); assertEquals(9, t2.getRowCount()); assertEquals(0L, t2.fetchRow(1).getTimestampAsTimestamp(0).getTime()); assertEquals(0L, t2.fetchRow(1).getTimestampAsLong(0)); assertEquals(1L, t2.fetchRow(2).getTimestampAsTimestamp(0).getTime()); assertEquals(1L, t2.fetchRow(2).getTimestampAsLong(0)); VoltTableRow r = t2.fetchRow(0); assertNull(r.getTimestampAsTimestamp(0)); assertTrue(r.wasNull()); r = t2.fetchRow(0); assertEquals(VoltType.NULL_BIGINT, r.getTimestampAsLong(0)); assertTrue(r.wasNull()); t2.clearRowData(); assertTrue(t2.getRowCount() == 0); } public void testAddRowExceptionSafe() { t = new VoltTable( new ColumnInfo("foo", VoltType.BIGINT), new ColumnInfo("bar", VoltType.STRING), new ColumnInfo("baz", VoltType.BIGINT) ); t.addRow(0L, "a", 1L); try { t.addRow(42L,"", "bad"); fail("expected exception"); } catch (VoltTypeException e) {} t.addRow(2L, "b", 3L); // the contents of the table should not be corrupted assertEquals(2, t.getRowCount()); assertEquals(0L, t.fetchRow(0).getLong(0)); assertEquals("a", t.fetchRow(0).getString(1)); assertEquals(1L, t.fetchRow(0).getLong(2)); assertEquals(2L, t.fetchRow(1).getLong(0)); assertEquals("b", t.fetchRow(1).getString(1)); assertEquals(3L, t.fetchRow(1).getLong(2)); } public void testClone() { VoltTable item_data_template = new VoltTable( new ColumnInfo("i_name", VoltType.STRING), new ColumnInfo("s_quantity", VoltType.BIGINT), new ColumnInfo("brand_generic", VoltType.STRING), new ColumnInfo("i_price", VoltType.FLOAT), new ColumnInfo("ol_amount", VoltType.FLOAT) ); VoltTable item_data = item_data_template.clone(1024); assertEquals(5, item_data.getColumnCount()); assertEquals("i_name", item_data.getColumnName(0)); assertEquals("s_quantity", item_data.getColumnName(1)); assertEquals("brand_generic", item_data.getColumnName(2)); assertEquals("i_price", item_data.getColumnName(3)); assertEquals("ol_amount", item_data.getColumnName(4)); assertEquals(VoltType.STRING, item_data.getColumnType(0)); assertEquals(VoltType.BIGINT, item_data.getColumnType(1)); assertEquals(VoltType.STRING, item_data.getColumnType(2)); assertEquals(VoltType.FLOAT, item_data.getColumnType(3)); assertEquals(VoltType.FLOAT, item_data.getColumnType(4)); item_data.addRow("asdfsdgfsdg", 123L, "a", 45.0d, 656.2d); } public void testRowIterator() { // Test iteration of empty table VoltTable empty = new VoltTable(); // make sure it craps out try { empty.getLong(1); fail(); } catch(Exception e) {}; assertEquals(-1, empty.getActiveRowIndex()); assertFalse(empty.advanceRow()); // Make a table with info to iterate t = new VoltTable(new ColumnInfo("foo", VoltType.BIGINT), new ColumnInfo("bar", VoltType.STRING)); for (int i = 0; i < 10; i++) { t.addRow(i, String.valueOf(i)); } int rowcount = 0; VoltTableRow copy = null; while (t.advanceRow()) { assertEquals(rowcount, t.getLong(0)); assertTrue(String.valueOf(rowcount).equals(t.getString(1))); if (rowcount == 4) copy = t.cloneRow(); rowcount++; } assertEquals(10, rowcount); rowcount = 5; while (copy.advanceRow()) { assertEquals(rowcount, copy.getLong(0)); assertTrue(String.valueOf(rowcount).equals(copy.getString(1))); rowcount++; } assertEquals(10, rowcount); } public void testStupidAdvanceRowUse() { VoltTable table = new VoltTable(new ColumnInfo("foo", VoltType.BIGINT)); table.addRow(5); // try to access value without calling advanceRow try { table.getLong(0); fail(); } catch (RuntimeException e) { assertTrue(e.getMessage().startsWith("VoltTableRow.advanceRow")); } } public void testRowGet() { byte b1 = (byte)1; short s1 = (short)2; int i1 = 3; long l1 = Long.MIN_VALUE + 1; double f1 = 3.5; String S1 = "A"; TimestampType d1 = new TimestampType(0); BigDecimal B1 = new BigDecimal(7654321).setScale(VoltDecimalHelper.kDefaultScale); // create a table with one column per supported type with NULLS on the left to right diagonal. // tinyint is intentionally first AND last to test that wasNull is correctly cleared by the // the next-to-last instance and re-initialized by tinyint. Object content[] = { b1, S1, i1, l1, f1, s1, d1, B1, b1 }; Object nulls[] = { VoltType.NULL_TINYINT, VoltType.NULL_STRING, VoltType.NULL_INTEGER, VoltType.NULL_BIGINT, VoltType.NULL_FLOAT, VoltType.NULL_SMALLINT, VoltType.NULL_TIMESTAMP, VoltType.NULL_DECIMAL, VoltType.NULL_TINYINT }; VoltType types[] = { VoltType.TINYINT, VoltType.STRING, VoltType.INTEGER, VoltType.BIGINT, VoltType.FLOAT, VoltType.SMALLINT, VoltType.TIMESTAMP, VoltType.DECIMAL, VoltType.TINYINT }; VoltTable tt = new VoltTable( new ColumnInfo("tinyint", types[0]), new ColumnInfo("string", types[1]), new ColumnInfo("integer", types[2]), new ColumnInfo("bigint", types[3]), new ColumnInfo("float", types[4]), new ColumnInfo("smallint", types[5]), new ColumnInfo("timestamp", types[6]), new ColumnInfo("decimal", types[7]), new ColumnInfo("tinyint", types[0])); for (int i=0; i < content.length; ++i) { Object[] vals = new Object[content.length];; for (int k=0; k < content.length; k++) { if (i == k) vals[k] = nulls[k]; else vals[k] = content[k]; } System.out.println("Adding row: " + i); tt.addRow(vals); } // now iterate all the fields in the table and verify that row.get(idx, type) // works and that the wasNull state is correctly set and cleared. System.out.println(tt); int rowcounter = 0; while (tt.advanceRow()) { for (int k =0; k < content.length; k++) { System.out.println("verifying row:" + rowcounter + " col:" + k); if (rowcounter == k) { boolean result = comparisonHelper(nulls[k], tt.get(k, types[k]), types[k]); assertTrue(result); assertTrue(tt.wasNull()); } else { Object got = tt.get(k, types[k]); System.out.println("Type: " + types[k]); System.out.println("Expecting: " + content[k]); System.out.println("Got: " + got); assertTrue(comparisonHelper(content[k], got, types[k])); assertFalse(tt.wasNull()); } } rowcounter++; } assertEquals(rowcounter, content.length); } }