/* * Copyright (c) 2010-2013, 2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.tests.db; import org.eclipse.emf.cdo.server.internal.db.DBStore; import org.eclipse.emf.cdo.tests.AbstractCDOTest; import org.eclipse.net4j.db.DBType; import org.eclipse.net4j.db.DBUtil; import org.eclipse.net4j.db.IDBDatabase; import org.eclipse.net4j.db.ddl.IDBSchema; import org.eclipse.net4j.db.ddl.IDBTable; import org.eclipse.net4j.db.oracle.OracleAdapter; import org.eclipse.net4j.spi.db.DBAdapter; import org.eclipse.net4j.util.collection.Pair; import org.eclipse.net4j.util.io.ExtendedDataInputStream; import org.eclipse.net4j.util.io.ExtendedDataOutputStream; import org.eclipse.net4j.util.io.ExtendedIOUtil; import org.eclipse.net4j.util.io.IOUtil; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.GregorianCalendar; import java.util.concurrent.TimeUnit; /** * @author Eike Stepper */ public class Net4jDBTest extends AbstractCDOTest { private static final String FIELD_NAME = "testField"; private transient ArrayList<Pair<DBType, Object>> columns = new ArrayList<Pair<DBType, Object>>(); @Override protected void doTearDown() throws Exception { columns.clear(); super.doTearDown(); } public void testBigInt() throws Exception { registerColumn(DBType.BIGINT, Long.MAX_VALUE); registerColumn(DBType.BIGINT, Long.MIN_VALUE); registerColumn(DBType.BIGINT, 0L); registerColumn(DBType.BIGINT, 42L); doTest(getName()); } public void testBinary() throws Exception { if (!isOracle()) { registerColumn(DBType.BINARY, new byte[0]); } byte[] data = new byte[100]; for (int i = 0; i < data.length; i++) { data[i] = (byte)(Math.random() * (Byte.MAX_VALUE - Byte.MIN_VALUE) + Byte.MIN_VALUE); } registerColumn(DBType.BINARY, data); doTest(getName()); } public void testVarBinary() throws Exception { if (!isOracle()) { registerColumn(DBType.VARBINARY, new byte[0]); } byte[] data = new byte[100]; for (int i = 0; i < data.length; i++) { data[i] = (byte)(Math.random() * (Byte.MAX_VALUE - Byte.MIN_VALUE) + Byte.MIN_VALUE); } registerColumn(DBType.VARBINARY, data); doTest(getName()); } public void testLongVarBinary() throws Exception { if (!isOracle()) { registerColumn(DBType.LONGVARBINARY, new byte[0]); } byte[] data = new byte[100]; for (int i = 0; i < data.length; i++) { data[i] = (byte)(Math.random() * (Byte.MAX_VALUE - Byte.MIN_VALUE) + Byte.MIN_VALUE); } registerColumn(DBType.LONGVARBINARY, data); doTest(getName()); } public void testBit() throws Exception { registerColumn(DBType.BIT, true); registerColumn(DBType.BIT, false); doTest(getName()); } public void testBlob() throws Exception { byte[] data = new byte[1000000]; for (int i = 0; i < data.length; i++) { data[i] = (byte)(Math.random() * (Byte.MAX_VALUE - Byte.MIN_VALUE) + Byte.MIN_VALUE); } registerColumn(DBType.BLOB, data); doTest(getName()); } @Skips("oracle") public void testBlobLength0() throws Exception { registerColumn(DBType.BLOB, new byte[0]); doTest(getName()); } public void testBoolean() throws Exception { registerColumn(DBType.BOOLEAN, true); registerColumn(DBType.BOOLEAN, false); doTest(getName()); } public void testChar() throws Exception { registerColumn(DBType.CHAR, "0"); registerColumn(DBType.CHAR, "a"); registerColumn(DBType.CHAR, "\377"); // Fails for DB2 registerColumn(DBType.CHAR, "\u1234"); // Fails for DB2 doTest(getName()); } public static void main(String[] args) { System.out.println((int)'\377'); } public void testClob() throws Exception { registerColumn(DBType.CLOB, "Test"); StringBuilder b = new StringBuilder(); for (int i = 0; i < 1000000; i++) { b.append("x"); } registerColumn(DBType.CLOB, b.toString()); doTest(getName()); } public void testClobLength0() throws Exception { registerColumn(DBType.CLOB, ""); doTest(getName()); } public void testTinyInt() throws Exception { registerColumn(DBType.TINYINT, Byte.MAX_VALUE); registerColumn(DBType.TINYINT, Byte.MIN_VALUE); registerColumn(DBType.TINYINT, new Byte("0")); registerColumn(DBType.TINYINT, new Integer(42).byteValue()); doTest(getName()); } public void testSmallInt() throws Exception { registerColumn(DBType.SMALLINT, Short.MAX_VALUE); registerColumn(DBType.SMALLINT, Short.MIN_VALUE); registerColumn(DBType.SMALLINT, (short)-1); registerColumn(DBType.SMALLINT, (short)5); doTest(getName()); } public void testInteger() throws Exception { registerColumn(DBType.INTEGER, Integer.MAX_VALUE); registerColumn(DBType.INTEGER, Integer.MIN_VALUE); registerColumn(DBType.INTEGER, -1); registerColumn(DBType.INTEGER, 5); doTest(getName()); } public void testFloat() throws Exception { registerColumn(DBType.FLOAT, Float.MAX_VALUE); registerColumn(DBType.FLOAT, Float.MIN_VALUE); // Fails for DB2 registerColumn(DBType.FLOAT, -.1f); registerColumn(DBType.FLOAT, 3.33333f); doTest(getName()); } public void testReal() throws Exception { registerColumn(DBType.REAL, Float.MAX_VALUE); registerColumn(DBType.REAL, Float.MIN_VALUE); // Fails for DB2 registerColumn(DBType.REAL, -.1f); registerColumn(DBType.REAL, 3.33333f); doTest(getName()); } public void testDouble() throws Exception { if (!isOracle()) { registerColumn(DBType.DOUBLE, new Double(Double.MAX_VALUE)); } // registerColumn(DBType.DOUBLE, new Double(Double.MIN_VALUE)); registerColumn(DBType.DOUBLE, -.1d); registerColumn(DBType.DOUBLE, 3.33333d); doTest(getName()); } public void _testNumeric() throws Exception { String numberLiteral1 = "12345678901234567890123456789012"; String numberLiteral2 = "10000000000000000000000000000000"; for (int precision = 1; precision < 32; precision++) { BigInteger numberInteger1 = new BigInteger(numberLiteral1.substring(0, precision)); BigInteger numberInteger2 = new BigInteger(numberLiteral2.substring(0, precision)); for (int scale = 0; scale <= precision; scale++) { BigDecimal numberDecimal1 = new BigDecimal(numberInteger1, scale); BigDecimal numberDecimal2 = new BigDecimal(numberInteger2, scale); registerColumn(DBType.NUMERIC, numberDecimal1); registerColumn(DBType.NUMERIC, numberDecimal2); doTest(getName() + precision + "_" + scale); columns.clear(); } } } public void _testDecimal() throws Exception { String numberLiteral1 = "12345678901234567890123456789012"; String numberLiteral2 = "10000000000000000000000000000000"; for (int precision = 1; precision < 32; precision++) { BigInteger numberInteger1 = new BigInteger(numberLiteral1.substring(0, precision)); BigInteger numberInteger2 = new BigInteger(numberLiteral2.substring(0, precision)); for (int scale = 0; scale <= precision; scale++) { BigDecimal numberDecimal1 = new BigDecimal(numberInteger1, scale); BigDecimal numberDecimal2 = new BigDecimal(numberInteger2, scale); registerColumn(DBType.DECIMAL, numberDecimal1); registerColumn(DBType.DECIMAL, numberDecimal2); doTest(getName() + precision + "_" + scale); columns.clear(); } } } public void testVarChar() throws Exception { registerColumn(DBType.VARCHAR, ""); // registerColumn(DBType.VARCHAR, null); // registerColumn(DBType.VARCHAR, " "); // registerColumn(DBType.VARCHAR, "\n"); // registerColumn(DBType.VARCHAR, "\t"); // registerColumn(DBType.VARCHAR, "\r"); // registerColumn(DBType.VARCHAR, "\u1234"); // registerColumn(DBType.VARCHAR, "The quick brown fox jumps over the lazy dog."); // registerColumn(DBType.VARCHAR, "\\,:\",\'"); doTest(getName()); } public void testLongVarChar() throws Exception { registerColumn(DBType.LONGVARCHAR, ""); if (!isOracle()) // Only 1 LONGVARCHAR allowed per table { registerColumn(DBType.LONGVARCHAR, "\n"); registerColumn(DBType.LONGVARCHAR, "\t"); registerColumn(DBType.LONGVARCHAR, "\r"); registerColumn(DBType.LONGVARCHAR, "\u1234"); registerColumn(DBType.LONGVARCHAR, "The quick brown fox jumps over the lazy dog."); registerColumn(DBType.LONGVARCHAR, "\\,:\",\'"); } doTest(getName()); } public void testDate() throws Exception { registerColumn(DBType.DATE, new GregorianCalendar(2010, 04, 21).getTimeInMillis()); registerColumn(DBType.DATE, new GregorianCalendar(1950, 04, 21).getTimeInMillis()); registerColumn(DBType.DATE, new GregorianCalendar(2030, 12, 31).getTimeInMillis()); if (!isOracle()) { registerColumn(DBType.DATE, new GregorianCalendar(0, 0, 0).getTimeInMillis()); // Fails for DB2 and Oracle } doTest(getName()); } public void testTime() throws Exception { registerColumn(DBType.TIME, HOURS_toMillis(10)); registerColumn(DBType.TIME, 0l); registerColumn(DBType.TIME, HOURS_toMillis(11) + MINUTES_toMillis(59) + TimeUnit.SECONDS.toMillis(59)); // Following tests fail on H2 as 24h == 1 day => 0 registerColumn(DBType.TIME, HOURS_toMillis(24)); doTest(getName()); } public void testTimestamp() throws Exception { registerColumn(DBType.TIME, HOURS_toMillis(10)); registerColumn(DBType.TIME, 0l); registerColumn(DBType.TIME, HOURS_toMillis(11) + MINUTES_toMillis(59) + TimeUnit.SECONDS.toMillis(59)); // Following tests fail on H2 as 24h == 1 day => 0 registerColumn(DBType.TIME, HOURS_toMillis(24)); doTest(getName()); } private void registerColumn(DBType type, Object value) throws IOException { testIOSymmetry(type, value); Pair<DBType, Object> column = Pair.create(type, value); columns.add(column); } private void testIOSymmetry(DBType type, Object value) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); ExtendedDataOutputStream outs = new ExtendedDataOutputStream(output); writeTypeValue(outs, type, value); outs.close(); output.flush(); byte[] buffer = output.toByteArray(); output.close(); ByteArrayInputStream input = new ByteArrayInputStream(buffer); ExtendedDataInputStream ins = new ExtendedDataInputStream(input); Object actualValue = readTypeValue(ins, type); assertEquals(value, actualValue, type, -1); } private void prepareTable(DBStore store, final String tableName) { IDBDatabase database = store.getDatabase(); database.updateSchema(new IDBDatabase.RunnableWithSchema() { public void run(IDBSchema schema) { IDBTable table = schema.addTable(tableName); int c = 0; for (Pair<DBType, Object> column : columns) { switch (column.getElement1()) { case NUMERIC: case DECIMAL: BigDecimal value = (BigDecimal)column.getElement2(); table.addField(FIELD_NAME + c++, column.getElement1(), value.precision(), value.scale()); break; default: table.addField(FIELD_NAME + c++, column.getElement1()); break; } } } }); } private void writeValues(Connection connection, String tableName) throws Exception { ByteArrayOutputStream output = new ByteArrayOutputStream(); ExtendedDataOutputStream outs = new ExtendedDataOutputStream(output); boolean first = true; StringBuilder builder = new StringBuilder("INSERT INTO " + tableName + " VALUES ("); for (Pair<DBType, Object> column : columns) { Object value = column.getElement2(); if (value instanceof String) { String str = (String)value; DBStore store = (DBStore)getRepository().getStore(); IDBDatabase database = store.getDatabase(); DBAdapter adapter = (DBAdapter)database.getAdapter(); value = adapter.convertString((PreparedStatement)null, 0, str); } writeTypeValue(outs, column.getElement1(), value); if (first) { builder.append("?"); first = false; } else { builder.append(", ?"); } } builder.append(")"); String sql = builder.toString(); outs.close(); output.flush(); byte[] buffer = output.toByteArray(); output.close(); ByteArrayInputStream input = new ByteArrayInputStream(buffer); ExtendedDataInputStream ins = new ExtendedDataInputStream(input); PreparedStatement stmt = connection.prepareStatement(sql); int c = 1; for (Pair<DBType, Object> column : columns) { column.getElement1().readValueWithResult(ins, stmt, c++, false); } stmt.executeUpdate(); stmt.close(); ins.close(); input.close(); } private void checkValues(Connection connection, String tableName) throws Exception { Statement stmt = connection.createStatement(); ResultSet resultSet = stmt.executeQuery("SELECT * FROM " + tableName); assertEquals(true, resultSet.next()); ByteArrayOutputStream output = new ByteArrayOutputStream(); ExtendedDataOutputStream outs = new ExtendedDataOutputStream(output); int c = 1; for (Pair<DBType, Object> column : columns) { DBType dbType = column.getElement1(); dbType.writeValue(outs, resultSet, c++, false); } resultSet.close(); stmt.close(); outs.close(); output.flush(); byte[] buffer = output.toByteArray(); output.close(); ByteArrayInputStream input = new ByteArrayInputStream(buffer); ExtendedDataInputStream ins = new ExtendedDataInputStream(input); c = 1; for (Pair<DBType, Object> column : columns) { DBType dbType = column.getElement1(); Object expected = column.getElement2(); Object actual = readTypeValue(ins, dbType); assertEquals(expected, actual, dbType, c++); } } private void assertEquals(Object expected, Object actual, DBType dbType, int c) { if (expected == null || actual == null) { assertEquals("Error in column " + c + " with type " + dbType, expected, actual); return; } Class<? extends Object> type = expected.getClass(); if (type.isArray()) { Class<?> componentType = type.getComponentType(); if (componentType == byte.class) { assertEquals("Error in column " + c + " of type " + dbType, true, Arrays.equals((byte[])expected, (byte[])actual)); } else if (componentType == char.class) { assertEquals("Error in column " + c + " with type " + dbType, true, Arrays.equals((char[])expected, (char[])actual)); } else { throw new IllegalStateException("Unexpected component type: " + componentType); } } else { if (dbType == DBType.TIME) { actual = (Long)actual % 86400000L; expected = (Long)expected % 86400000L; } assertEquals("Error in column " + c + " with type " + dbType, expected, actual); } } private void doTest(String tableName) throws Exception { DBStore store = (DBStore)getRepository().getStore(); Connection connection = store.getConnection(); try { prepareTable(store, tableName); writeValues(connection, tableName); checkValues(connection, tableName); } finally { try { connection.commit(); } finally { DBUtil.close(connection); connection = null; store = null; } } } private void writeTypeValue(ExtendedDataOutputStream outs, DBType type, Object value) throws IOException { switch (type) { case BOOLEAN: case BIT: outs.writeBoolean((Boolean)value); return; case TINYINT: outs.writeByte((Byte)value); return; case CHAR: outs.writeString((String)value); return; case SMALLINT: outs.writeShort((Short)value); return; case INTEGER: outs.writeInt((Integer)value); return; case FLOAT: outs.writeFloat((Float)value); return; case REAL: outs.writeFloat((Float)value); return; case DOUBLE: outs.writeDouble((Double)value); return; case NUMERIC: case DECIMAL: { BigDecimal bigDecimal = (BigDecimal)value; outs.writeByteArray(bigDecimal.unscaledValue().toByteArray()); outs.writeInt(bigDecimal.scale()); return; } case VARCHAR: case LONGVARCHAR: outs.writeString((String)value); return; case CLOB: ExtendedIOUtil.writeCharacterStream(outs, new StringReader((String)value)); return; case BIGINT: case DATE: case TIME: case TIMESTAMP: outs.writeLong((Long)value); return; case BINARY: case VARBINARY: case LONGVARBINARY: outs.writeByteArray((byte[])value); return; case BLOB: ExtendedIOUtil.writeBinaryStream(outs, new ByteArrayInputStream((byte[])value)); return; default: throw new UnsupportedOperationException("not implemented"); } } private Object readTypeValue(ExtendedDataInputStream ins, DBType type) throws IOException { switch (type) { case BOOLEAN: case BIT: return ins.readBoolean(); case CHAR: return ins.readString(); case TINYINT: return ins.readByte(); case SMALLINT: return ins.readShort(); case INTEGER: return ins.readInt(); case FLOAT: case REAL: return ins.readFloat(); case DOUBLE: return ins.readDouble(); case NUMERIC: case DECIMAL: { byte[] array = ins.readByteArray(); if (array == null) { return null; } BigInteger unscaled = new BigInteger(array); int scale = ins.readInt(); return new BigDecimal(unscaled, scale); } case VARCHAR: case LONGVARCHAR: DBStore store = (DBStore)getRepository().getStore(); IDBDatabase database = store.getDatabase(); DBAdapter adapter = (DBAdapter)database.getAdapter(); return adapter.convertString((ResultSet)null, 0, ins.readString()); case CLOB: { StringWriter result = new StringWriter(); try { ExtendedIOUtil.readCharacterStream(ins, result); } finally { IOUtil.close(result); } return result.toString(); } case DATE: case BIGINT: case TIME: case TIMESTAMP: return ins.readLong(); case BINARY: case VARBINARY: case LONGVARBINARY: return ins.readByteArray(); case BLOB: { ByteArrayOutputStream result = new ByteArrayOutputStream(); try { ExtendedIOUtil.readBinaryStream(ins, result); } finally { IOUtil.close(result); } return result.toByteArray(); } default: throw new UnsupportedOperationException("not implemented"); } } private long HOURS_toMillis(int hours) { return MINUTES_toMillis(60 * hours); } private long MINUTES_toMillis(int minutes) { return 1000L * 60L * minutes; } private boolean isOracle() { DBStore store = (DBStore)getRepository().getStore(); return OracleAdapter.NAME.equals(store.getDBAdapter().getName()); } }