Skip to content
Browse files

Frist commit

  • Loading branch information...
0 parents commit 14fd15ade747d267b016c2c013137856b6a08f35 @poetix poetix committed
19 LICENSE
@@ -0,0 +1,19 @@
+High Speed Dirt. Copyright (C) 2012 youDevise, Ltd.
+
+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 OR COPYRIGHT HOLDERS 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.
5 README.markdown
@@ -0,0 +1,5 @@
+= High Speed Dirt =
+
+Sometimes you just want to zip through a large JDBC result set, one record at a time, with as little marshalling overhead as possible.
+
+HSD provides a programmer-friendly, type-safe way to do this whilst minimising data copying and object creation/garbage collection.
112 pom.xml
@@ -0,0 +1,112 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.youdevise</groupId>
+ <artifactId>hsd</artifactId>
+ <packaging>jar</packaging>
+ <name>High Speed Dirt</name>
+ <version>0.1-SNAPSHOT</version>
+ <description>Very fast database record retrieval</description>
+ <url>https://github.com/youdevise/hsd</url>
+ <licenses>
+ <license>
+ <name>The MIT License (MIT)</name>
+ <url>http://www.opensource.org/licenses/mit-license.php</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <url>git@github.com:youdevise/hsd.git</url>
+ <connection>scm:git:git@github.com:youdevise/hsd.git</connection>
+ <developerConnection>scm:git:git@github.com:youdevise/hsd.git</developerConnection>
+ </scm>
+ <developers>
+ <developer>
+ <id>...</id>
+ <name>Dominic Fox</name>
+ <email>dominic.fox@timgroup.com</email>
+ </developer>
+ </developers>
+ <dependencies>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>5.0.8</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>10.0.1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <version>1.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ <version>2.5.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit-dep</artifactId>
+ <version>4.8.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <defaultGoal>compile</defaultGoal>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.8</version>
+ <configuration>
+ ...
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <repositories>
+ <repository>
+ <id>Forumarchivebuilder</id>
+ <url>http://forumarchivebuilder.googlecode.com/svn/repository</url>
+ </repository>
+ </repositories>
+
+ <!--
+ AVOID RELEASE REPOSITORY/PLUGINREPOSITORY:
+ <repositories></repositories>
+ <pluginRepositories></pluginRepositories>
+ -->
+</project>
12 src/main/java/com/youdevise/hsd/Column.java
@@ -0,0 +1,12 @@
+package com.youdevise.hsd;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Column {
+ String value();
+}
36 src/main/java/com/youdevise/hsd/DefaultColumnNameTransformer.java
@@ -0,0 +1,36 @@
+package com.youdevise.hsd;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+public class DefaultColumnNameTransformer<E extends Enum<E>> implements Function<E, String> {
+
+ public static <E extends Enum<E>> DefaultColumnNameTransformer<E> forEnumClass(Class<E> enumClass) {
+ return forEnumClass(enumClass, Functions.<String>identity());
+ }
+
+ public static <E extends Enum<E>> DefaultColumnNameTransformer<E> forEnumClass(Class<E> enumClass,
+ Function<String, String> nameTransformer) {
+ return new DefaultColumnNameTransformer<E>(enumClass, nameTransformer);
+ }
+
+ private final Class<E> enumClass;
+ private final Function<String, String> nameTransformer;
+
+ private DefaultColumnNameTransformer(Class<E> enumClass, Function<String, String> nameTransformer) {
+ this.enumClass = enumClass;
+ this.nameTransformer = nameTransformer;
+ }
+
+ @Override
+ public String apply(E value) {
+ try {
+ Column columnAnnotation = enumClass.getField(value.name()).getAnnotation(Column.class);
+ return columnAnnotation == null
+ ? nameTransformer.apply(value.name())
+ : columnAnnotation.value();
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
9 src/main/java/com/youdevise/hsd/EnumIndexedCursor.java
@@ -0,0 +1,9 @@
+package com.youdevise.hsd;
+
+import java.util.EnumMap;
+
+public interface EnumIndexedCursor<E extends Enum<E>> {
+ <T> T get(E key);
+ EnumMap<E, Object> values();
+ boolean next();
+}
54 src/main/java/com/youdevise/hsd/MetadataToEnumMapTransformer.java
@@ -0,0 +1,54 @@
+package com.youdevise.hsd;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.EnumMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+public class MetadataToEnumMapTransformer<E extends Enum<E>> implements Function<ResultSetMetaData, EnumMap<E, Integer>> {
+
+ public static <E extends Enum<E>> MetadataToEnumMapTransformer<E> forEnumClass(Class<E> enumClass) {
+ return forEnumClass(enumClass, DefaultColumnNameTransformer.forEnumClass(enumClass));
+ }
+
+ public static <E extends Enum<E>> MetadataToEnumMapTransformer<E> forEnumClass(Class<E> enumClass,
+ Function<E, String> columnNameTransformer) {
+ return new MetadataToEnumMapTransformer<E>(enumClass,columnNameTransformer);
+ }
+
+ private final Class<E> enumClass;
+ private final Function<E, String> columnNameTransformer;
+
+ private MetadataToEnumMapTransformer(Class<E> enumClass, Function<E, String> columnNameTransformer) {
+ this.enumClass = enumClass;
+ this.columnNameTransformer = columnNameTransformer;
+ }
+
+ @Override public EnumMap<E, Integer> apply(ResultSetMetaData metadata) {
+ Map<String, Integer> columnIndices = getColumnIndices(metadata);
+ EnumMap<E, Integer> indices = new EnumMap<E, Integer>(enumClass);
+ for (E value : enumClass.getEnumConstants()) {
+ String columnName = columnNameTransformer.apply(value);
+ Integer index = columnIndices.get(columnName);
+ Preconditions.checkNotNull(index, "No column found named ", columnName);
+ indices.put(value, index);
+ }
+ return indices;
+ }
+
+ private Map<String, Integer> getColumnIndices(ResultSetMetaData metadata) {
+ ImmutableMap.Builder<String, Integer> indices = ImmutableMap.builder();
+ try {
+ for (int i=0; i<metadata.getColumnCount(); i++) {
+ indices.put(metadata.getColumnName(i), i);
+ }
+ return indices.build();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
61 src/main/java/com/youdevise/hsd/ResultSetAdapter.java
@@ -0,0 +1,61 @@
+package com.youdevise.hsd;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.EnumMap;
+import java.util.Map;
+
+
+public class ResultSetAdapter<E extends Enum<E>> implements EnumIndexedCursor<E> {
+
+ public static <E extends Enum<E>> ResultSetAdapter<E> adapting(ResultSet resultSet, Class<E> enumClass) {
+ MetadataToEnumMapTransformer<E> transformer = MetadataToEnumMapTransformer.forEnumClass(enumClass);
+ EnumMap<E, Integer> indices;
+ try {
+ indices = transformer.apply(resultSet.getMetaData());
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ return new ResultSetAdapter<E>(resultSet, enumClass, indices);
+ }
+
+ private final ResultSet resultSet;
+ private final Class<E> enumClass;
+ private final EnumMap<E, Integer> indices;
+
+ private ResultSetAdapter(ResultSet resultSet, Class<E> enumClass, EnumMap<E, Integer> indices) {
+ this.resultSet = resultSet;
+ this.enumClass = enumClass;
+ this.indices = indices;
+ }
+
+ @Override public <T> T get(E key) {
+ return get(indices.get(key));
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T get(int index) {
+ try {
+ return (T) resultSet.getObject(index);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override public boolean next() {
+ try {
+ return resultSet.next();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override public EnumMap<E, Object> values() {
+ EnumMap<E, Object> values = new EnumMap<E, Object>(enumClass);
+ for (Map.Entry<E, Integer> entry : indices.entrySet()) {
+ values.put(entry.getKey(), get(entry.getValue()));
+ }
+ return values;
+ }
+
+}
66 src/test/java/com/youdevise/hsd/DefaultColumnNameTransformerTest.java
@@ -0,0 +1,66 @@
+package com.youdevise.hsd;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.junit.Test;
+
+import com.google.common.base.Function;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+public class DefaultColumnNameTransformerTest {
+
+ private final Mockery context = new Mockery();
+
+ public enum TestEnum {
+ foo,
+ @Column("bar_id") bar,
+ bazId
+ }
+
+ @Test public void
+ converts_enum_value_name_to_string() {
+ DefaultColumnNameTransformer<TestEnum> transformer = DefaultColumnNameTransformer.forEnumClass(TestEnum.class);
+
+ assertThat(transformer.apply(TestEnum.foo), equalTo("foo"));
+ }
+
+ @Test public void
+ takes_column_name_from_annotation_where_supplied() {
+ DefaultColumnNameTransformer<TestEnum> transformer = DefaultColumnNameTransformer.forEnumClass(TestEnum.class);
+
+ assertThat(transformer.apply(TestEnum.bar), equalTo("bar_id"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test public void
+ converts_enum_values_using_name_transformer_if_supplied() {
+ final Function<String, String> nameTransformer = context.mock(Function.class);
+
+ context.checking(new Expectations() {{
+ oneOf(nameTransformer).apply("bazId"); will(returnValue("baz_id"));
+ }});
+
+ DefaultColumnNameTransformer<TestEnum> transformer = DefaultColumnNameTransformer.forEnumClass(TestEnum.class, nameTransformer);
+
+ assertThat(transformer.apply(TestEnum.bazId), equalTo("baz_id"));
+ context.assertIsSatisfied();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test public void
+ does_not_convert_names_provided_by_annotation() {
+ final Function<String, String> nameTransformer = context.mock(Function.class);
+
+ context.checking(new Expectations() {{
+ never(nameTransformer);
+ }});
+
+ DefaultColumnNameTransformer<TestEnum> transformer = DefaultColumnNameTransformer.forEnumClass(TestEnum.class, nameTransformer);
+
+ assertThat(transformer.apply(TestEnum.bar), equalTo("bar_id"));
+ context.assertIsSatisfied();
+ }
+
+}
80 src/test/java/com/youdevise/hsd/MetadataToEnumMapTransformerTest.java
@@ -0,0 +1,80 @@
+package com.youdevise.hsd;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.EnumMap;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.base.Function;
+
+public class MetadataToEnumMapTransformerTest {
+ private final Mockery context = new Mockery();
+
+ private final ResultSet resultSet = context.mock(ResultSet.class);
+ private final ResultSetMetaData metadata = context.mock(ResultSetMetaData.class);
+
+ @Before public void
+ setup_mock_resultset() throws SQLException {
+ context.checking(new Expectations() {{
+ allowing(resultSet).getMetaData(); will(returnValue(metadata));
+
+ allowing(metadata).getColumnCount(); will(returnValue(3));
+ allowing(metadata).getColumnName(2); will(returnValue("foo"));
+ allowing(metadata).getColumnName(1); will(returnValue("bar"));
+ allowing(metadata).getColumnName(0); will(returnValue("baz"));
+ }});
+ }
+
+ public static enum TestEnum1 {
+ foo,
+ bar,
+ baz
+ }
+
+ @Test public void
+ maps_enum_values_to_field_indices() {
+ MetadataToEnumMapTransformer<TestEnum1> transformer = MetadataToEnumMapTransformer.forEnumClass(TestEnum1.class);
+
+ EnumMap<TestEnum1, Integer> indices = transformer.apply(metadata);
+
+ MatcherAssert.assertThat(indices.get(TestEnum1.foo), Matchers.is(2));
+ MatcherAssert.assertThat(indices.get(TestEnum1.bar), Matchers.is(1));
+ MatcherAssert.assertThat(indices.get(TestEnum1.baz), Matchers.is(0));
+ }
+
+ public static enum TestEnum2 {
+ FOO,
+ BAR,
+ BAZ
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test public void
+ uses_supplied_transformer_to_map_enum_values_to_column_names() {
+ final Function<TestEnum2, String> columnNameTransformer = context.mock(Function.class);
+
+ context.checking(new Expectations() {{
+ oneOf(columnNameTransformer).apply(TestEnum2.FOO); will(returnValue("foo"));
+ oneOf(columnNameTransformer).apply(TestEnum2.BAR); will(returnValue("bar"));
+ oneOf(columnNameTransformer).apply(TestEnum2.BAZ); will(returnValue("baz"));
+ }});
+
+ MetadataToEnumMapTransformer<TestEnum2> transformer = MetadataToEnumMapTransformer.forEnumClass(TestEnum2.class, columnNameTransformer);
+
+ EnumMap<TestEnum2, Integer> indices = transformer.apply(metadata);
+
+ MatcherAssert.assertThat(indices.get(TestEnum2.FOO), Matchers.is(2));
+ MatcherAssert.assertThat(indices.get(TestEnum2.BAR), Matchers.is(1));
+ MatcherAssert.assertThat(indices.get(TestEnum2.BAZ), Matchers.is(0));
+
+ context.assertIsSatisfied();
+ }
+
+}
72 src/test/java/com/youdevise/hsd/ResultSetAdapterTest.java
@@ -0,0 +1,72 @@
+package com.youdevise.hsd;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.EnumMap;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.junit.Before;
+import org.junit.Test;
+
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+public class ResultSetAdapterTest {
+
+ private final Mockery context = new Mockery();
+
+ private final ResultSet resultSet = context.mock(ResultSet.class);
+ private final ResultSetMetaData metadata = context.mock(ResultSetMetaData.class);
+ private final Date theDate = new Date();
+
+ @Before public void
+ setup_stub_resultset() throws SQLException {
+ context.checking(new Expectations() {{
+ allowing(resultSet).getMetaData(); will(returnValue(metadata));
+
+ allowing(metadata).getColumnCount(); will(returnValue(3));
+ allowing(metadata).getColumnName(2); will(returnValue("foo"));
+ allowing(metadata).getColumnName(1); will(returnValue("bar"));
+ allowing(metadata).getColumnName(0); will(returnValue("baz_id"));
+
+ allowing(resultSet).getObject(2); will(returnValue("Hello"));
+ allowing(resultSet).getObject(1); will(returnValue(23));
+ allowing(resultSet).getObject(0); will(returnValue(theDate));
+ }});
+ }
+
+ public static interface Record {
+ public enum Fields {
+ foo,
+ bar,
+ @Column("baz_id") baz
+ }
+
+ public String getFoo();
+ public int getBar();
+ public Date getBaz();
+ }
+
+ @Test public void
+ looks_up_key_indices_in_recordset_metadata_using_column_annotations_where_present() throws SQLException {
+ ResultSetAdapter<Record.Fields> adapter = ResultSetAdapter.adapting(resultSet, Record.Fields.class);
+
+ assertThat(adapter.<String>get(Record.Fields.foo), equalTo("Hello"));
+ assertThat(adapter.<Integer>get(Record.Fields.bar), equalTo(23));
+ assertThat(adapter.<Date>get(Record.Fields.baz), equalTo(theDate));
+ }
+
+ @Test public void
+ exports_result_data_as_enum_map() throws SQLException {
+ ResultSetAdapter<Record.Fields> adapter = ResultSetAdapter.adapting(resultSet, Record.Fields.class);
+ EnumMap<Record.Fields, Object> values = adapter.values();
+
+ assertThat((String) values.get(Record.Fields.foo), equalTo("Hello"));
+ assertThat((Integer) values.get(Record.Fields.bar), equalTo(23));
+ assertThat((Date) values.get(Record.Fields.baz), equalTo(theDate));
+ }
+}

0 comments on commit 14fd15a

Please sign in to comment.
Something went wrong with that request. Please try again.