aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClemens Fries <ormpaloompa@xenoworld.de>2016-05-19 00:28:53 +0200
committerxeno <xeno@eisberg.nacht>2016-05-19 00:36:56 +0200
commit6138a5ce200d0c1ebc2730fd565bc7bcfdcdc230 (patch)
treeef15e317477f9f3530f1b9f0cfb8810eaebc56ca /src
parent5fe09722dbe590d6624f635093baccc6dd8973db (diff)
Fix alternative name handling for columns, plus a lot of documentation.
Diffstat (limited to 'src')
-rw-r--r--src/main/java/de/xenoworld/ormpaloompa/Table.java126
-rw-r--r--src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java29
-rw-r--r--src/main/java/de/xenoworld/ormpaloompa/annotations/Column.java (renamed from src/main/java/de/xenoworld/ormpaloompa/annotations/Field.java)10
-rw-r--r--src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java2
-rw-r--r--src/main/java/overview.adoc36
-rw-r--r--src/test/java/de/xenoworld/ormpaloompa/TableTest.java43
-rw-r--r--src/test/java/de/xenoworld/ormpaloompa/testutils/DBRule.java5
7 files changed, 174 insertions, 77 deletions
diff --git a/src/main/java/de/xenoworld/ormpaloompa/Table.java b/src/main/java/de/xenoworld/ormpaloompa/Table.java
index 96497b6..6d23fcd 100644
--- a/src/main/java/de/xenoworld/ormpaloompa/Table.java
+++ b/src/main/java/de/xenoworld/ormpaloompa/Table.java
@@ -1,9 +1,10 @@
package de.xenoworld.ormpaloompa;
-import de.xenoworld.ormpaloompa.annotations.Field;
+import de.xenoworld.ormpaloompa.annotations.Column;
import de.xenoworld.ormpaloompa.annotations.TableInfo;
import org.apache.commons.lang3.tuple.Pair;
+import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -16,10 +17,9 @@ import java.util.stream.Stream;
// TODO: Filter out fields where the value is null
// TODO: Filter out the identity fields when updating
-// (insert identities only when they are not null → auto IDs, but things like the term-database do
-// have a string as key)
+// (insert identities only when they are not null → auto IDs, but things
+// like the term-database do have a string as key)
// TODO: Use more Optionals and get rid of exception throwing where possible
-// Name: Ormpaloompa
/**
* A SQLite table of a POJO `T` as a proxy.
*
@@ -31,10 +31,10 @@ import java.util.stream.Stream;
* [source,java]
* ----
* class Bird {
- * @Field(identity = true)
+ * @Column(identity = true)
* public Integer id;
*
- * @Field
+ * @Column
* public String name;
* }
* ----
@@ -57,9 +57,12 @@ public class Table<T> {
private final Connection connection;
private final String tableName;
- private final TableField identity;
- private ArrayList<TableField> tableFields;
+ private final TableColumn identity;
+ private ArrayList<TableColumn> tableColumns;
+ /**
+ * A central location for all query strings.
+ */
private enum Query {
COUNT("SELECT COUNT(*) FROM %s"),
GET_BY_ID("SELECT * FROM %s WHERE %s = ?"),
@@ -79,23 +82,33 @@ public class Table<T> {
}
}
-
- private class TableField {
+ /**
+ * Describes a column in the database.
+ *
+ * Properties are:
+ * `field`::
+ * The annotated field.
+ * optional `name`::
+ * Can be used to specify an alternative name for a column.
+ * optional `identity`::
+ * Used to mark the primary key. If omitted, the column with the name
+ * `Id` is used.
+ */
+ private class TableColumn {
final String name;
- final String dflt;
+ final Field field;
final boolean identity;
- private TableField(String name, String dflt, boolean identity) {
+ private TableColumn(Field field, String name, boolean identity) {
+ this.field = field;
this.name = name;
- this.dflt = dflt;
this.identity = identity;
}
@Override
public String toString() {
- return "TableField{" +
+ return "TableColumn{" +
"name='" + name + '\'' +
- ", dflt='" + dflt + '\'' +
", identity=" + identity +
'}';
}
@@ -105,14 +118,14 @@ public class Table<T> {
this.tableClass = tableClass;
this.connection = connection;
- tableFields = new ArrayList<>();
+ tableColumns = new ArrayList<>();
Stream.of(tableClass.getFields())
- .map(field -> Pair.of(field.getName(), field.getAnnotation(Field.class)))
- .forEach(p -> tableFields.add(
- new TableField(
- p.getRight().name().isEmpty() ? p.getLeft() : p.getRight().name(),
- p.getRight().dflt(),
+ .map(field -> Pair.of(field, field.getAnnotation(Column.class)))
+ .forEach(p -> tableColumns.add(
+ new TableColumn(
+ p.getLeft(),
+ p.getRight().name().isEmpty() ? p.getLeft().getName() : p.getRight().name(),
p.getRight().identity())));
this.tableName = discoverTableName(tableClass);
@@ -120,12 +133,12 @@ public class Table<T> {
}
// TODO: This is kind of an idiotic contraption
- private TableField discoverIdentity(Class<T> tableClass) {
- return tableFields.stream()
- .filter(f -> f.identity)
+ private TableColumn discoverIdentity(Class<T> tableClass) {
+ return tableColumns.stream()
+ .filter(c -> c.identity)
.findFirst()
- .orElseGet(() -> tableFields.stream()
- .filter(f -> f.name.toLowerCase().equals("id"))
+ .orElseGet(() -> tableColumns.stream()
+ .filter(c -> c.name.toLowerCase().equals("id"))
.findFirst()
.orElseThrow(() -> new RuntimeException(
String.format("%s: No identity given, and no field 'id' present.",
@@ -139,8 +152,8 @@ public class Table<T> {
* Get name for table from TableInfo annotation or append "s" to the lower
* case table name.
*
- * @param tableClass table to inspect
- * @return name of table in database
+ * @param tableClass table to inspect.
+ * @return name of table in database.
*/
private String discoverTableName(Class<T> tableClass) {
TableInfo tableInfo = tableClass.getAnnotation(TableInfo.class);
@@ -154,7 +167,7 @@ public class Table<T> {
/**
* Return the number of rows in the table, uses `COUNT(*)`.
*
- * @return total number of rows
+ * @return total number of rows.
* @throws SQLException
*/
public Integer count() throws SQLException {
@@ -168,7 +181,7 @@ public class Table<T> {
/**
* Get a `T` by Identity.
*
- * @param id identity
+ * @param id identity.
* @return `Optional<T>`, `empty()` if no object could be found, or if an
* error occurred.
*/
@@ -208,9 +221,9 @@ public class Table<T> {
*
* This will create a query for `… WHERE foo = bar …`.
*
- * @param whereQuery a query
- * @param args (optional) arguments to WHERE clause
- * @return a Stream of matching `T`
+ * @param whereQuery a query.
+ * @param args (optional) arguments to WHERE clause.
+ * @return a Stream of matching `T`.
*/
public Stream<T> find(WhereQuery whereQuery, Object... args) {
// TODO: Make try-mess better
@@ -236,28 +249,44 @@ public class Table<T> {
}
}
+ /**
+ * Create a new `T` from a ResultSet.
+ *
+ * @param result ResultSet to construct `T` from.
+ * @return a new `T`.
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ */
private T fromResultSet(ResultSet result) throws InstantiationException, IllegalAccessException {
T t = tableClass.newInstance();
- tableFields.forEach(f -> {
+ tableColumns.forEach(c -> {
try {
- t.getClass().getField(f.name).set(t, result.getObject(f.name));
+ t.getClass().getField(c.field.getName()).set(t, result.getObject(c.name));
} catch (IllegalAccessException | NoSuchFieldException | SQLException e) {
e.printStackTrace();
}
});
+
return t;
}
+ /**
+ * Insert a `T` into the database.
+ *
+ * @param t `T` to insert.
+ * @return an insert id.
+ * @throws SQLException
+ */
public Long insert(T t) throws SQLException {
- String fields = tableFields
+ String fields = tableColumns
.stream()
.map(f -> f.name)
.collect(Collectors.joining(","));
StringBuffer placeholder = new StringBuffer("?");
- IntStream.range(0, tableFields.size() - 1).forEach(i -> placeholder.append(", ?"));
+ IntStream.range(0, tableColumns.size() - 1).forEach(i -> placeholder.append(", ?"));
String query = String.format(Query.INSERT.toString(), tableName, fields, placeholder);
PreparedStatement stmt = connection.prepareStatement(query);
@@ -269,8 +298,13 @@ public class Table<T> {
return stmt.getGeneratedKeys().getLong(1);
}
+ /**
+ * Update an existing `T`.
+ *
+ * @param t `T` to update.
+ */
public void update(T t) {
- String fields = tableFields
+ String fields = tableColumns
.stream()
.map(f -> f.name)
.collect(Collectors.joining(" = ?, "));
@@ -290,9 +324,9 @@ public class Table<T> {
private int fillStatementFromObject(T t, PreparedStatement stmt) {
final int[] order = {1};
- tableFields.stream().forEach(f -> {
+ tableColumns.stream().forEach(c -> {
try {
- Object o = t.getClass().getField(f.name).get(t);
+ Object o = t.getClass().getField(c.field.getName()).get(t);
stmt.setObject(order[0]++, o);
} catch (IllegalAccessException | NoSuchFieldException | SQLException e) {
e.printStackTrace();
@@ -303,10 +337,10 @@ public class Table<T> {
}
/**
- * Take `t` and insert a new entry, or update an existing entry.
+ * Insert `T`, or update if it already exists.
*
- * @param t
- * @return Optional of insert Id (a Long)
+ * @param t `T` to insert or update.
+ * @return a `Optional<>` of insert id (a `Long`).
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws SQLException
@@ -328,13 +362,13 @@ public class Table<T> {
}
/**
- * Retrieve the current value of the `@Identity` field.
+ * Retrieve the current value of the `@Column(identity = true)` field.
*
* Will return an empty Optional if the value is null, or if there was
* an Exception.
*
- * @param t object to retrieve identity value from
- * @return value of identity, or empty on null or error
+ * @param t object to retrieve identity value from.
+ * @return value of identity, or empty on null or error.
*/
public Optional<Object> retrieveIdValue(T t) {
try {
diff --git a/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java b/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java
index fcc42eb..4047be4 100644
--- a/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java
+++ b/src/main/java/de/xenoworld/ormpaloompa/WhereQuery.java
@@ -3,7 +3,9 @@ package de.xenoworld.ormpaloompa;
import java.util.Optional;
public class WhereQuery {
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Optional<Integer> limit = Optional.empty();
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Optional<String> orderFields = Optional.empty();
private Order order;
private String where;
@@ -34,17 +36,40 @@ public class WhereQuery {
this.where = where;
}
+ /**
+ * Set `LIMIT` on `WHERE` clause.
+ *
+ * @param limit maximum number of results to return.
+ * @return same WhereQuery.
+ */
public WhereQuery limit(Integer limit) {
this.limit = Optional.of(limit);
return this;
}
- public WhereQuery orderBy(String fields, Order order) {
- orderFields = Optional.of(fields);
+ /**
+ * Set `ORDER BY` on `WHERE` clause.
+ *
+ * `columns` may be one column name, or multiple names separated by comma.
+ *
+ * @param columns one or more columns to `ORDER BY`
+ * @param order ascending or descending order.
+ * @return same WhereQuery.
+ */
+ public WhereQuery orderBy(String columns, Order order) {
+ orderFields = Optional.of(columns);
this.order = order;
return this;
}
+ /**
+ * Add the `WHERE` query.
+ *
+ * This is something like `foo = 1234` or `bar = ?`.
+ *
+ * @param where the actual where query.
+ * @return same WhereQuery.
+ */
public static WhereQuery where(String where) {
return new WhereQuery(where);
}
diff --git a/src/main/java/de/xenoworld/ormpaloompa/annotations/Field.java b/src/main/java/de/xenoworld/ormpaloompa/annotations/Column.java
index 5cd917b..92a917b 100644
--- a/src/main/java/de/xenoworld/ormpaloompa/annotations/Field.java
+++ b/src/main/java/de/xenoworld/ormpaloompa/annotations/Column.java
@@ -7,10 +7,14 @@ import java.lang.annotation.RetentionPolicy;
* Annotate a public field.
*/
@Retention(RetentionPolicy.RUNTIME)
-public @interface Field {
+public @interface Column {
+ /**
+ * If non-empty, the column name to be used.
+ */
String name() default "";
- String dflt() default "";
-
+ /**
+ * `true` if the field is the table's identity.
+ */
boolean identity() default false;
} \ No newline at end of file
diff --git a/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java b/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java
index 5094c5d..04c2d23 100644
--- a/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java
+++ b/src/main/java/de/xenoworld/ormpaloompa/annotations/TableInfo.java
@@ -4,7 +4,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Optional table annotation, can be used to set the table name in the database
+ * Optional table annotation, can be used to set the table name in the database.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface TableInfo {
diff --git a/src/main/java/overview.adoc b/src/main/java/overview.adoc
index 90a82f6..90642bb 100644
--- a/src/main/java/overview.adoc
+++ b/src/main/java/overview.adoc
@@ -1,18 +1,32 @@
-= Eduard XVII — The loyal MUC servant
+== Ormpaloompa — Sort-of-ORM for EduardXVII
-== Continuous Integration
+Because the friendly MUC chat bot EduardXVII needs to deal in several places
+with simple-structured SQLite tables, I created this tiny object-relational
+mapper. It stinks in several places, but it also gets its job done. Coverage is
+over 85% and should go further north.
-We currently use the CI of Gitlab.com, but it might be warranted to use a
-dedicated runner which could execute the requests much faster. This is work
-for Future Homer.
+It mainly consists of the class `Table` which can be used as follows.
-Currently everything is configured in the file `.gitlab-ci.yml`:
+As simple class could look like this:
-[source,yaml]
+[source,java]
----
-include::../../../.gitlab-ci.yml[]
+class Bird {
+ @Column(identity = true)
+ public Integer id;
+
+ @Column
+ public String name;
+}
----
-NOTE: Replacing `ec.SunEC` is done here because there were problems in the
- Docker container with OpenJDK. Apparently making a HTTPS connection
- to the Gradle repositories would not work. \ No newline at end of file
+Then create a `Table<Bird>` object and use it to interact with the database.
+
+[source,java]
+----
+Table<Bird> t = new Table<>(Bird.class, someDatabaseConnection);
+Bird newBird = new Bird();
+newBird.name = "A new bird";
+Object id = table.insert(newBird);
+Bird storedBird = t.getById(id).get();
+----
diff --git a/src/test/java/de/xenoworld/ormpaloompa/TableTest.java b/src/test/java/de/xenoworld/ormpaloompa/TableTest.java
index 79b3a0c..d73b45f 100644
--- a/src/test/java/de/xenoworld/ormpaloompa/TableTest.java
+++ b/src/test/java/de/xenoworld/ormpaloompa/TableTest.java
@@ -1,6 +1,6 @@
package de.xenoworld.ormpaloompa;
-import de.xenoworld.ormpaloompa.annotations.Field;
+import de.xenoworld.ormpaloompa.annotations.Column;
import de.xenoworld.ormpaloompa.annotations.TableInfo;
import de.xenoworld.ormpaloompa.testutils.DBRule;
import org.junit.Ignore;
@@ -18,27 +18,27 @@ import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
/**
- * Test object with explicit identity
+ * Test object with explicit identity, and an alternative name for a field.
*/
class Bird {
- @Field(identity = true)
+ @Column(identity = true)
public Integer id;
- @Field
+ @Column
public String name;
- @Field
+ @Column(name = "descr")
public String description;
}
/**
- * Test object with no explicit identity
+ * Test object with no explicit identity.
*/
class Rabbit {
- @Field
+ @Column
public Integer id;
- @Field
+ @Column
public String name;
}
@@ -48,10 +48,10 @@ class Rabbit {
*/
@TableInfo(tableName = "foxes")
class Fox {
- @Field(identity = true)
+ @Column(identity = true)
public String name;
- @Field(identity = true)
+ @Column(identity = true)
public Integer id;
}
@@ -60,13 +60,13 @@ class Fox {
*/
@TableInfo
class Monkey {
- @Field
+ @Column
public String name;
}
public class TableTest {
static String[] FIXTURES = {
- "CREATE TABLE birds (id INTEGER PRIMARY KEY, name TEXT, description TEXT);",
+ "CREATE TABLE birds (id INTEGER PRIMARY KEY, name TEXT, descr TEXT);",
"INSERT INTO birds " +
"(id, name) " +
"VALUES " +
@@ -100,7 +100,7 @@ public class TableTest {
public DBRule dbRule = new DBRule(FIXTURES);
/**
- * The field `id` should be annotated as `@Field`, but it should not have
+ * The field `id` should be annotated as `@Column`, but it should not have
* property `identity` set to `true`. The field should be recognised as
* identity by virtue of being named `id`.
*/
@@ -239,4 +239,21 @@ public class TableTest {
assertThat("Table size did not change", table.count(), is(4));
assertThat("No new entries were added", insertId.isPresent(), is(false));
}
+
+ @Test
+ public void testFind_brokenQuery() throws Exception {
+ long count;
+ Table<Bird> table = new Table<>(Bird.class, dbRule.getConnection());
+
+ Bird newBird = new Bird();
+ newBird.name = "A new bird";
+
+ table.insert(newBird);
+
+ count = table.find(where("id = 1")).count();
+ assertThat("Table has one entry", count, is(1L));
+
+ count = table.find(where("broken = 1234")).count();
+ assertThat("Query returns turns up empty", count, is(0L));
+ }
} \ No newline at end of file
diff --git a/src/test/java/de/xenoworld/ormpaloompa/testutils/DBRule.java b/src/test/java/de/xenoworld/ormpaloompa/testutils/DBRule.java
index 0bb12f7..39da3d8 100644
--- a/src/test/java/de/xenoworld/ormpaloompa/testutils/DBRule.java
+++ b/src/test/java/de/xenoworld/ormpaloompa/testutils/DBRule.java
@@ -2,7 +2,10 @@ package de.xenoworld.ormpaloompa.testutils;
import org.junit.rules.ExternalResource;
-import java.sql.*;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
import java.util.stream.Stream;
public class DBRule extends ExternalResource {