/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.standard.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import org.apache.avro.Conversions;
import org.apache.avro.Schema;
import org.apache.avro.file.DataFileStream;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumReader;
import org.apache.avro.util.Utf8;
import org.apache.commons.io.input.ReaderInputStream;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class TestJdbcCommon {
static final String createTable = "create table restaurants(id integer, name varchar(20), city varchar(50))";
static final String dropTable = "drop table restaurants";
@ClassRule
public static TemporaryFolder folder = new TemporaryFolder();
/**
* Setting up Connection is expensive operation.
* So let's do this only once and reuse Connection in each test.
*/
static protected Connection con;
@BeforeClass
public static void setup() throws ClassNotFoundException, SQLException {
System.setProperty("derby.stream.error.file", "target/derby.log");
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
// remove previous test database, if any
folder.delete();
String location = folder.getRoot().getAbsolutePath();
con = DriverManager.getConnection("jdbc:derby:" + location + ";create=true");
try (final Statement stmt = con.createStatement()) {
stmt.executeUpdate(createTable);
}
}
@Test
public void testCreateSchema() throws ClassNotFoundException, SQLException {
final Statement st = con.createStatement();
st.executeUpdate("insert into restaurants values (1, 'Irifunes', 'San Mateo')");
st.executeUpdate("insert into restaurants values (2, 'Estradas', 'Daly City')");
st.executeUpdate("insert into restaurants values (3, 'Prime Rib House', 'San Francisco')");
final ResultSet resultSet = st.executeQuery("select * from restaurants");
final Schema schema = JdbcCommon.createSchema(resultSet);
assertNotNull(schema);
// records name, should be result set first column table name
// Notice! sql select may join data from different tables, other columns
// may have different table names
assertEquals("RESTAURANTS", schema.getName());
assertNotNull(schema.getField("ID"));
assertNotNull(schema.getField("NAME"));
assertNotNull(schema.getField("CITY"));
st.close();
// con.close();
}
@Test
public void testCreateSchemaNoColumns() throws ClassNotFoundException, SQLException {
final ResultSet resultSet = mock(ResultSet.class);
final ResultSetMetaData resultSetMetaData = mock(ResultSetMetaData.class);
when(resultSet.getMetaData()).thenReturn(resultSetMetaData);
when(resultSetMetaData.getColumnCount()).thenReturn(0);
when(resultSetMetaData.getTableName(1)).thenThrow(SQLException.class);
final Schema schema = JdbcCommon.createSchema(resultSet);
assertNotNull(schema);
// records name, should be result set first column table name
// Notice! sql select may join data from different tables, other columns
// may have different table names
assertEquals("NiFi_ExecuteSQL_Record", schema.getName());
assertNull(schema.getField("ID"));
}
@Test
public void testCreateSchemaNoTableName() throws ClassNotFoundException, SQLException {
final ResultSet resultSet = mock(ResultSet.class);
final ResultSetMetaData resultSetMetaData = mock(ResultSetMetaData.class);
when(resultSet.getMetaData()).thenReturn(resultSetMetaData);
when(resultSetMetaData.getColumnCount()).thenReturn(1);
when(resultSetMetaData.getTableName(1)).thenReturn("");
when(resultSetMetaData.getColumnType(1)).thenReturn(Types.INTEGER);
when(resultSetMetaData.getColumnName(1)).thenReturn("ID");
final Schema schema = JdbcCommon.createSchema(resultSet);
assertNotNull(schema);
// records name, should be result set first column table name
assertEquals("NiFi_ExecuteSQL_Record", schema.getName());
}
@Test
public void testCreateSchemaOnlyColumnLabel() throws ClassNotFoundException, SQLException {
final ResultSet resultSet = mock(ResultSet.class);
final ResultSetMetaData resultSetMetaData = mock(ResultSetMetaData.class);
when(resultSet.getMetaData()).thenReturn(resultSetMetaData);
when(resultSetMetaData.getColumnCount()).thenReturn(2);
when(resultSetMetaData.getTableName(1)).thenReturn("TEST");
when(resultSetMetaData.getColumnType(1)).thenReturn(Types.INTEGER);
when(resultSetMetaData.getColumnName(1)).thenReturn("");
when(resultSetMetaData.getColumnLabel(1)).thenReturn("ID");
when(resultSetMetaData.getColumnType(2)).thenReturn(Types.VARCHAR);
when(resultSetMetaData.getColumnName(2)).thenReturn("VCHARC");
when(resultSetMetaData.getColumnLabel(2)).thenReturn("NOT_VCHARC");
final Schema schema = JdbcCommon.createSchema(resultSet);
assertNotNull(schema);
assertNotNull(schema.getField("ID"));
assertNotNull(schema.getField("NOT_VCHARC"));
// records name, should be result set first column table name
assertEquals("TEST", schema.getName());
}
@Test
public void testConvertToBytes() throws ClassNotFoundException, SQLException, IOException {
final Statement st = con.createStatement();
st.executeUpdate("insert into restaurants values (1, 'Irifunes', 'San Mateo')");
st.executeUpdate("insert into restaurants values (2, 'Estradas', 'Daly City')");
st.executeUpdate("insert into restaurants values (3, 'Prime Rib House', 'San Francisco')");
final ResultSet resultSet = st.executeQuery("select R.*, ROW_NUMBER() OVER () as rownr from restaurants R");
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
JdbcCommon.convertToAvroStream(resultSet, outStream, false);
final byte[] serializedBytes = outStream.toByteArray();
assertNotNull(serializedBytes);
System.out.println("Avro serialized result size in bytes: " + serializedBytes.length);
st.close();
// Deserialize bytes to records
final InputStream instream = new ByteArrayInputStream(serializedBytes);
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>();
try (final DataFileStream<GenericRecord> dataFileReader = new DataFileStream<>(instream, datumReader)) {
GenericRecord record = null;
while (dataFileReader.hasNext()) {
// Reuse record object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
record = dataFileReader.next(record);
System.out.println(record);
}
}
}
@Test
public void testCreateSchemaTypes() throws SQLException, IllegalArgumentException, IllegalAccessException {
final Set<Integer> fieldsToIgnore = new HashSet<>();
fieldsToIgnore.add(Types.NULL);
fieldsToIgnore.add(Types.OTHER);
final Field[] fieldTypes = Types.class.getFields();
for (final Field field : fieldTypes) {
final Object fieldObject = field.get(null);
final int type = (int) fieldObject;
if (fieldsToIgnore.contains(Types.NULL)) {
continue;
}
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
when(metadata.getColumnCount()).thenReturn(1);
when(metadata.getColumnType(1)).thenReturn(type);
when(metadata.getColumnName(1)).thenReturn(field.getName());
when(metadata.getTableName(1)).thenReturn("table");
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
try {
JdbcCommon.createSchema(rs);
} catch (final IllegalArgumentException | SQLException sqle) {
sqle.printStackTrace();
Assert.fail("Failed when using type " + field.getName());
}
}
}
@Test
public void testSignedIntShouldBeInt() throws SQLException, IllegalArgumentException, IllegalAccessException {
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
when(metadata.getColumnCount()).thenReturn(1);
when(metadata.getColumnType(1)).thenReturn(Types.INTEGER);
when(metadata.isSigned(1)).thenReturn(true);
when(metadata.getColumnName(1)).thenReturn("Col1");
when(metadata.getTableName(1)).thenReturn("Table1");
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
Schema schema = JdbcCommon.createSchema(rs);
Assert.assertNotNull(schema);
Schema.Field field = schema.getField("Col1");
Schema fieldSchema = field.schema();
Assert.assertEquals(2, fieldSchema.getTypes().size());
boolean foundIntSchema = false;
boolean foundNullSchema = false;
for (Schema type : fieldSchema.getTypes()) {
if (type.getType().equals(Schema.Type.INT)) {
foundIntSchema = true;
} else if (type.getType().equals(Schema.Type.NULL)) {
foundNullSchema = true;
}
}
assertTrue(foundIntSchema);
assertTrue(foundNullSchema);
}
@Test
public void testUnsignedIntShouldBeLong() throws SQLException, IllegalArgumentException, IllegalAccessException {
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
when(metadata.getColumnCount()).thenReturn(1);
when(metadata.getColumnType(1)).thenReturn(Types.INTEGER);
when(metadata.getPrecision(1)).thenReturn(10);
when(metadata.isSigned(1)).thenReturn(false);
when(metadata.getColumnName(1)).thenReturn("Col1");
when(metadata.getTableName(1)).thenReturn("Table1");
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
Schema schema = JdbcCommon.createSchema(rs);
Assert.assertNotNull(schema);
Schema.Field field = schema.getField("Col1");
Schema fieldSchema = field.schema();
Assert.assertEquals(2, fieldSchema.getTypes().size());
boolean foundLongSchema = false;
boolean foundNullSchema = false;
for (Schema type : fieldSchema.getTypes()) {
if (type.getType().equals(Schema.Type.LONG)) {
foundLongSchema = true;
} else if (type.getType().equals(Schema.Type.NULL)) {
foundNullSchema = true;
}
}
assertTrue(foundLongSchema);
assertTrue(foundNullSchema);
}
@Test
public void testMediumUnsignedIntShouldBeInt() throws SQLException, IllegalArgumentException, IllegalAccessException {
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
when(metadata.getColumnCount()).thenReturn(1);
when(metadata.getColumnType(1)).thenReturn(Types.INTEGER);
when(metadata.getPrecision(1)).thenReturn(8);
when(metadata.isSigned(1)).thenReturn(false);
when(metadata.getColumnName(1)).thenReturn("Col1");
when(metadata.getTableName(1)).thenReturn("Table1");
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
Schema schema = JdbcCommon.createSchema(rs);
Assert.assertNotNull(schema);
Schema.Field field = schema.getField("Col1");
Schema fieldSchema = field.schema();
Assert.assertEquals(2, fieldSchema.getTypes().size());
boolean foundIntSchema = false;
boolean foundNullSchema = false;
for (Schema type : fieldSchema.getTypes()) {
if (type.getType().equals(Schema.Type.INT)) {
foundIntSchema = true;
} else if (type.getType().equals(Schema.Type.NULL)) {
foundNullSchema = true;
}
}
assertTrue(foundIntSchema);
assertTrue(foundNullSchema);
}
@Test
public void testConvertToAvroStreamForBigDecimal() throws SQLException, IOException {
final BigDecimal bigDecimal = new BigDecimal(38D);
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
when(metadata.getColumnCount()).thenReturn(1);
when(metadata.getColumnType(1)).thenReturn(Types.NUMERIC);
when(metadata.getColumnName(1)).thenReturn("The.Chairman");
when(metadata.getTableName(1)).thenReturn("1the::table");
when(metadata.getPrecision(1)).thenReturn(bigDecimal.precision());
when(metadata.getScale(1)).thenReturn(bigDecimal.scale());
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
final AtomicInteger counter = new AtomicInteger(1);
Mockito.doAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return counter.getAndDecrement() > 0;
}
}).when(rs).next();
when(rs.getObject(Mockito.anyInt())).thenReturn(bigDecimal);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final JdbcCommon.AvroConversionOptions options = JdbcCommon.AvroConversionOptions
.builder().convertNames(true).useLogicalTypes(true).build();
JdbcCommon.convertToAvroStream(rs, baos, options, null);
final byte[] serializedBytes = baos.toByteArray();
final InputStream instream = new ByteArrayInputStream(serializedBytes);
final GenericData genericData = new GenericData();
genericData.addLogicalTypeConversion(new Conversions.DecimalConversion());
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>(null, null, genericData);
try (final DataFileStream<GenericRecord> dataFileReader = new DataFileStream<>(instream, datumReader)) {
GenericRecord record = null;
while (dataFileReader.hasNext()) {
record = dataFileReader.next(record);
assertEquals("_1the__table", record.getSchema().getName());
assertEquals(bigDecimal, record.get("The_Chairman"));
}
}
}
@Test
public void testClob() throws Exception {
try (final Statement stmt = con.createStatement()) {
stmt.executeUpdate("CREATE TABLE clobtest (id INT, text CLOB(64 K))");
stmt.execute("INSERT INTO blobtest VALUES (41, NULL)");
PreparedStatement ps = con.prepareStatement("INSERT INTO clobtest VALUES (?, ?)");
ps.setInt(1, 42);
final char[] buffer = new char[4002];
IntStream.range(0, 4002).forEach((i) -> buffer[i] = String.valueOf(i % 10).charAt(0));
ReaderInputStream isr = new ReaderInputStream(new CharArrayReader(buffer), Charset.defaultCharset());
// - set the value of the input parameter to the input stream
ps.setAsciiStream(2, isr, 4002);
ps.execute();
isr.close();
final ResultSet resultSet = stmt.executeQuery("select * from clobtest");
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
JdbcCommon.convertToAvroStream(resultSet, outStream, false);
final byte[] serializedBytes = outStream.toByteArray();
assertNotNull(serializedBytes);
// Deserialize bytes to records
final InputStream instream = new ByteArrayInputStream(serializedBytes);
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>();
try (final DataFileStream<GenericRecord> dataFileReader = new DataFileStream<>(instream, datumReader)) {
GenericRecord record = null;
while (dataFileReader.hasNext()) {
// Reuse record object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
record = dataFileReader.next(record);
Integer id = (Integer) record.get("ID");
Object o = record.get("TEXT");
if (id == 41) {
assertNull(o);
} else {
assertNotNull(o);
assertEquals(4002, o.toString().length());
}
}
}
}
}
@Test
public void testBlob() throws Exception {
try (final Statement stmt = con.createStatement()) {
stmt.executeUpdate("CREATE TABLE blobtest (id INT, b BLOB(64 K))");
stmt.execute("INSERT INTO blobtest VALUES (41, NULL)");
PreparedStatement ps = con.prepareStatement("INSERT INTO blobtest VALUES (?, ?)");
ps.setInt(1, 42);
final byte[] buffer = new byte[4002];
IntStream.range(0, 4002).forEach((i) -> buffer[i] = (byte) ((i % 10) + 65));
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
// - set the value of the input parameter to the input stream
ps.setBlob(2, bais, 4002);
ps.execute();
bais.close();
final ResultSet resultSet = stmt.executeQuery("select * from blobtest");
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
JdbcCommon.convertToAvroStream(resultSet, outStream, false);
final byte[] serializedBytes = outStream.toByteArray();
assertNotNull(serializedBytes);
// Deserialize bytes to records
final InputStream instream = new ByteArrayInputStream(serializedBytes);
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>();
try (final DataFileStream<GenericRecord> dataFileReader = new DataFileStream<>(instream, datumReader)) {
GenericRecord record = null;
while (dataFileReader.hasNext()) {
// Reuse record object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
record = dataFileReader.next(record);
Integer id = (Integer) record.get("ID");
Object o = record.get("B");
if (id == 41) {
assertNull(o);
} else {
assertNotNull(o);
assertTrue(o instanceof ByteBuffer);
assertEquals(4002, ((ByteBuffer) o).array().length);
}
}
}
}
}
@Test
public void testConvertToAvroStreamForShort() throws SQLException, IOException {
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
when(metadata.getColumnCount()).thenReturn(1);
when(metadata.getColumnType(1)).thenReturn(Types.TINYINT);
when(metadata.getColumnName(1)).thenReturn("t_int");
when(metadata.getTableName(1)).thenReturn("table");
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
final AtomicInteger counter = new AtomicInteger(1);
Mockito.doAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return counter.getAndDecrement() > 0;
}
}).when(rs).next();
final short s = 25;
when(rs.getObject(Mockito.anyInt())).thenReturn(s);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
JdbcCommon.convertToAvroStream(rs, baos, false);
final byte[] serializedBytes = baos.toByteArray();
final InputStream instream = new ByteArrayInputStream(serializedBytes);
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>();
try (final DataFileStream<GenericRecord> dataFileReader = new DataFileStream<>(instream, datumReader)) {
GenericRecord record = null;
while (dataFileReader.hasNext()) {
record = dataFileReader.next(record);
assertEquals(Short.toString(s), record.get("t_int").toString());
}
}
}
@Test
public void testConvertToAvroStreamForDateTimeAsString() throws SQLException, IOException, ParseException {
final JdbcCommon.AvroConversionOptions options = JdbcCommon.AvroConversionOptions
.builder().convertNames(true).useLogicalTypes(false).build();
testConvertToAvroStreamForDateTime(options,
(record, date) -> assertEquals(new Utf8(date.toString()), record.get("date")),
(record, time) -> assertEquals(new Utf8(time.toString()), record.get("time")),
(record, timestamp) -> assertEquals(new Utf8(timestamp.toString()), record.get("timestamp"))
);
}
@Test
public void testConvertToAvroStreamForDateTimeAsLogicalType() throws SQLException, IOException, ParseException {
final JdbcCommon.AvroConversionOptions options = JdbcCommon.AvroConversionOptions
.builder().convertNames(true).useLogicalTypes(true).build();
testConvertToAvroStreamForDateTime(options,
(record, date) -> {
final int daysSinceEpoch = (int) record.get("date");
final long millisSinceEpoch = TimeUnit.MILLISECONDS.convert(daysSinceEpoch, TimeUnit.DAYS);
assertEquals(date, new java.sql.Date(millisSinceEpoch));
},
(record, time) -> assertEquals(time, new Time((int) record.get("time"))),
(record, timestamp) -> assertEquals(timestamp, new Timestamp((long) record.get("timestamp")))
);
}
private void testConvertToAvroStreamForDateTime(
JdbcCommon.AvroConversionOptions options, BiConsumer<GenericRecord, java.sql.Date> assertDate,
BiConsumer<GenericRecord, Time> assertTime, BiConsumer<GenericRecord, Timestamp> assertTimeStamp)
throws SQLException, IOException, ParseException {
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
BiFunction<String, String, Long> toMillis = (format, dateStr) -> {
try {
final SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat.parse(dateStr).getTime();
} catch (ParseException e) {
throw new RuntimeException(e);
}
};
when(metadata.getColumnCount()).thenReturn(3);
when(metadata.getTableName(anyInt())).thenReturn("table");
when(metadata.getColumnType(1)).thenReturn(Types.DATE);
when(metadata.getColumnName(1)).thenReturn("date");
final java.sql.Date date = new java.sql.Date(toMillis.apply("yyyy/MM/dd", "2017/05/10"));
when(rs.getObject(1)).thenReturn(date);
when(metadata.getColumnType(2)).thenReturn(Types.TIME);
when(metadata.getColumnName(2)).thenReturn("time");
final Time time = new Time(toMillis.apply("HH:mm:ss.SSS", "12:34:56.789"));
when(rs.getObject(2)).thenReturn(time);
when(metadata.getColumnType(3)).thenReturn(Types.TIMESTAMP);
when(metadata.getColumnName(3)).thenReturn("timestamp");
final Timestamp timestamp = new Timestamp(toMillis.apply("yyyy/MM/dd HH:mm:ss.SSS", "2017/05/11 19:59:39.123"));
when(rs.getObject(3)).thenReturn(timestamp);
final AtomicInteger counter = new AtomicInteger(1);
Mockito.doAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return counter.getAndDecrement() > 0;
}
}).when(rs).next();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
JdbcCommon.convertToAvroStream(rs, baos, options, null);
final byte[] serializedBytes = baos.toByteArray();
final InputStream instream = new ByteArrayInputStream(serializedBytes);
final DatumReader<GenericRecord> datumReader = new GenericDatumReader<>();
try (final DataFileStream<GenericRecord> dataFileReader = new DataFileStream<>(instream, datumReader)) {
GenericRecord record = null;
while (dataFileReader.hasNext()) {
record = dataFileReader.next(record);
assertDate.accept(record, date);
assertTime.accept(record, time);
assertTimeStamp.accept(record, timestamp);
}
}
}
// many test use Derby as database, so ensure driver is available
@Test
public void testDriverLoad() throws ClassNotFoundException {
final Class<?> clazz = Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
assertNotNull(clazz);
}
}