diff --git a/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java b/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java index 8e12385..ca7efb3 100644 --- a/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java +++ b/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java @@ -78,15 +78,15 @@ public SQLConnectionBuilder withDriver(String driver) { return this; } - public SQLDatabaseConnectionImpl build() { + public SQLDatabaseConnection build() { return build(null); } - public SQLDatabaseConnectionImpl build(@Nullable SQLDatabaseOptions options) { + public SQLDatabaseConnection build(@Nullable SQLDatabaseOptions options) { return build(driver, options); } - public SQLDatabaseConnectionImpl build(@Nullable String driver, @Nullable SQLDatabaseOptions options) { + public SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabaseOptions options) { Objects.requireNonNull(endpoint, "Endpoint must be set!"); Objects.requireNonNull(jdbc); if(driver == null) { diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java index 5601107..bfb03d6 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java @@ -33,6 +33,37 @@ public SQLDatabaseConnection(SQLConnectionFactory connectionFactory) { SQLConnectionPool.register(this); } + /** + * Constructs a mapping repository based on provided interface. + * The interface should follow rules for creating mapping repositories + * in this library. + *

+ * Example: + *

+     *     @Table("users")
+     *     public interface MyRepository {
+     *          @Select("*")
+     *          @Where(@Where.Condition(column = "firstname", value = "{First Name}"))
+     *          @Limit(1)
+     *          Optional<User> getUser(@Placeholder("First Name") String firstName);
+     *
+     *          @Select
+     *          List<User> getUsers();
+     *
+     *          @Delete
+     *          QueryResult deleteUsers();
+     *     }
+     *
+     *     SQLDatabaseConnection connection = ...;
+     *     MyRepository repository = connection.createGate(MyRepository.class);
+     *
+     *     Optional<User> user = repository.getUser("John");
+     * 
+ * + * @param mappingInterface Interface to create mapping repository for. + * @return Mapping repository. + * @param Type of mapping repository. + */ @ApiStatus.Experimental public abstract T createGate(Class mappingInterface); diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java index 00f2360..9d3d819 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java @@ -39,6 +39,7 @@ * * @author ZorTik */ +@SuppressWarnings("unused") public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection { // --***-- Default Constants --***-- @@ -123,6 +124,28 @@ public void setObjectMapper(@NotNull ObjectMapper objectMapper) { * Constructs a mapping repository based on provided interface. * The interface should follow rules for creating mapping repositories * in this library. + *

+ * Example: + *

+     *     @Table("users")
+     *     public interface MyRepository {
+     *          @Select("*")
+     *          @Where(@Where.Condition(column = "firstname", value = "{First Name}"))
+     *          @Limit(1)
+     *          Optional<User> getUser(@Placeholder("First Name") String firstName);
+     *
+     *          @Select
+     *          List<User> getUsers();
+     *
+     *          @Delete
+     *          QueryResult deleteUsers();
+     *     }
+     *
+     *     SQLDatabaseConnection connection = ...;
+     *     MyRepository repository = connection.createGate(MyRepository.class);
+     *
+     *     Optional<User> user = repository.getUser("John");
+     * 
* * @param mappingInterface Interface to create mapping repository for. * @return Mapping repository. @@ -130,7 +153,9 @@ public void setObjectMapper(@NotNull ObjectMapper objectMapper) { */ @SuppressWarnings("unchecked") @ApiStatus.Experimental - public T createGate(Class mappingInterface) { + public final T createGate(Class mappingInterface) { + Objects.requireNonNull(mappingInterface, "Mapping interface cannot be null!"); + StatementMappingStrategy statementMapping = mappingFactory.create(mappingInterface, this); return (T) Proxy.newProxyInstance(mappingInterface.getClassLoader(), new Class[]{mappingInterface}, (proxy, method, args) -> { @@ -145,15 +170,35 @@ public T createGate(Class mappingInterface) { return mappingResultAdapter.adaptResult(method, result); } - return method.invoke(this, args); + throw new UnsupportedOperationException("Method " + method.getName() + " is not supported by this mapping repository!"); }); } /** - * @see SQLDatabaseConnection#query(Query, Class) + * Performs new query and returns the result. This result is never null. + * See: {@link QueryRowsResult#isSuccessful()} + * + * Examples: + *

+ * query(Select.of().from("players"), Player.class) + * .stream() + * .map(Player::getNickname) + * .forEach(System.out::println); + *

+ * query(() -> "SELECT * FROM players;"); + * + * @param query The query to use while constructing query string. + * @param typeClass Type class of object which will be instantiated and + * populated with column values. + * @param Type of objects in result. + * + * @return Collection of row objects. */ @Override public QueryRowsResult query(Query query, Class typeClass) { + Objects.requireNonNull(query); + Objects.requireNonNull(typeClass); + QueryRowsResult resultRows = query(query.getAncestor()); QueryRowsResult result = new QueryRowsResult<>(resultRows.isSuccessful()); @@ -165,7 +210,9 @@ public QueryRowsResult query(Query query, Class typeClass) { } /** - * @see SQLDatabaseConnectionImpl#query(Query, Class) + * Performs new query and returns the result. This result is never null. + * + * @see SQLDatabaseConnection#query(Query, Class) */ @Override public QueryRowsResult query(Query query) { @@ -200,7 +247,14 @@ public QueryRowsResult query(Query query) { } /** - * @see SQLDatabaseConnection#exec(Query) + * Executes given query and returns execution result. + * This result does not contain any rows. If you want to + * execute query return result of rows, see method + * {@link SQLDatabaseConnection#query(Query)} + * + * @param query Query to use for building query string. + * @return Blank rows result that only informs + * about success state of the request. */ public QueryResult exec(Query query) { if(!handleAutoReconnect()) { @@ -216,7 +270,14 @@ public QueryResult exec(Query query) { } /** - * @see SQLDatabaseConnection#save(String, Object) + * Saves this mapping object into database using upsert query. + *

+ * All mapping strategies are described in: + * {@link SQLDatabaseConnection#query(Query, Class)}. + * + * @param table Table to save into. + * @param obj The object to save. + * @return Result of the query. */ @Override public QueryResult save(String table, Object obj) { // by default, it creates and upsert request. @@ -285,6 +346,7 @@ protected Pair buildDefsVals(Object obj) { return new Pair<>(defs, vals); } + @SuppressWarnings("all") private boolean handleAutoReconnect() { if(options.isAutoReconnect() && !isConnected()) { debug("Trying to make a new connection with the database!"); @@ -296,6 +358,8 @@ private boolean handleAutoReconnect() { return true; } + // --***-- Query builders --***-- + public SelectQuery select(String... cols) { return new SelectQuery(this, cols); } @@ -367,6 +431,16 @@ public boolean isDebug() { return options.isDebug(); } + public final SQLDatabaseOptions cloneOptions() { + SQLDatabaseOptions cloned = new SQLDatabaseOptions(); + cloned.setDebug(options.isDebug()); + cloned.setLogSqlErrors(options.isLogSqlErrors()); + cloned.setNamingStrategy(options.getNamingStrategy()); + cloned.setGson(options.getGson()); + cloned.setAutoReconnect(options.isAutoReconnect()); + return cloned; + } + @SuppressWarnings("unchecked") private PreparedStatement buildStatement(Query query) throws SQLException { StatementFactory factory = new DefaultStatementFactory(query); diff --git a/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java b/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java index 91635bd..0f7305b 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java @@ -36,6 +36,15 @@ public QueryNode(@Nullable P parent, List> initial, int priority) { this.details = new ConcurrentHashMap<>(); } + /** + * Builds the query string with placeholders containing values + * for passing into PreparedStatement. + *

+ * Query example: SELECT * FROM table WHERE id = <id>; + * Values example: [AnyId] + * + * @return QueryDetails object. + */ public abstract QueryDetails buildQueryDetails(); @Override diff --git a/core/src/main/java/me/zort/sqllib/mapping/QueryAnnotation.java b/core/src/main/java/me/zort/sqllib/mapping/QueryAnnotation.java index 791c269..ae6db2c 100644 --- a/core/src/main/java/me/zort/sqllib/mapping/QueryAnnotation.java +++ b/core/src/main/java/me/zort/sqllib/mapping/QueryAnnotation.java @@ -7,6 +7,7 @@ import me.zort.sqllib.internal.query.QueryNode; import me.zort.sqllib.mapping.annotation.*; import me.zort.sqllib.mapping.builder.DeleteQueryBuilder; +import me.zort.sqllib.mapping.builder.InsertQueryBuilder; import me.zort.sqllib.mapping.builder.SaveQueryBuilder; import me.zort.sqllib.mapping.builder.SelectQueryBuilder; import me.zort.sqllib.mapping.exception.SQLMappingException; @@ -40,6 +41,7 @@ public class QueryAnnotation { QUERY_ANNOT.put(Select.class, new QueryAnnotation(true, new SelectQueryBuilder())); QUERY_ANNOT.put(Delete.class, new QueryAnnotation(false, new DeleteQueryBuilder())); QUERY_ANNOT.put(Save.class, new QueryAnnotation(false, new SaveQueryBuilder())); + QUERY_ANNOT.put(Insert.class, new QueryAnnotation(false, new InsertQueryBuilder())); // TODO: Populate } @@ -66,10 +68,6 @@ public interface QueryBuilder { } public static class Validator { - public static void requireTableDefinition(Method method, PlaceholderMapper placeholderMapper) { - if (Table.Util.getFromContext(method, placeholderMapper) == null) - throw new SQLMappingException("Method " + method.getName() + " requires @Table annotation", method, null); - } public static void requireWhereDefinition(Method method) { if (!method.isAnnotationPresent(Where.class)) throw new SQLMappingException("Method " + method.getName() + " requires @Where annotation", method, null); diff --git a/core/src/main/java/me/zort/sqllib/mapping/annotation/Insert.java b/core/src/main/java/me/zort/sqllib/mapping/annotation/Insert.java new file mode 100644 index 0000000..69fc910 --- /dev/null +++ b/core/src/main/java/me/zort/sqllib/mapping/annotation/Insert.java @@ -0,0 +1,15 @@ +package me.zort.sqllib.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Insert { + + String[] cols(); + String[] vals(); + +} diff --git a/core/src/main/java/me/zort/sqllib/mapping/annotation/Table.java b/core/src/main/java/me/zort/sqllib/mapping/annotation/Table.java index d405b70..767e9b9 100644 --- a/core/src/main/java/me/zort/sqllib/mapping/annotation/Table.java +++ b/core/src/main/java/me/zort/sqllib/mapping/annotation/Table.java @@ -1,6 +1,9 @@ package me.zort.sqllib.mapping.annotation; import me.zort.sqllib.mapping.PlaceholderMapper; +import me.zort.sqllib.mapping.QueryAnnotation; +import me.zort.sqllib.mapping.exception.SQLMappingException; +import me.zort.sqllib.util.ParameterPair; import org.jetbrains.annotations.Nullable; import java.lang.annotation.ElementType; @@ -16,13 +19,14 @@ class Util { @Nullable - public static String getFromContext(Method method, PlaceholderMapper mapper) { + public static String getFromContext(Method method, ParameterPair[] parameters) { + PlaceholderMapper mapper = new PlaceholderMapper(parameters); if (method.isAnnotationPresent(Table.class)) { return mapper.assignValues(method.getAnnotation(Table.class).value()); } else if(method.getDeclaringClass().isAnnotationPresent(Table.class)) { return mapper.assignValues(method.getDeclaringClass().getAnnotation(Table.class).value()); } else { - return null; + throw new SQLMappingException("Method " + method.getName() + " requires @Table annotation", method, null); } } } diff --git a/core/src/main/java/me/zort/sqllib/mapping/builder/DeleteQueryBuilder.java b/core/src/main/java/me/zort/sqllib/mapping/builder/DeleteQueryBuilder.java index 0e808a3..50b1e80 100644 --- a/core/src/main/java/me/zort/sqllib/mapping/builder/DeleteQueryBuilder.java +++ b/core/src/main/java/me/zort/sqllib/mapping/builder/DeleteQueryBuilder.java @@ -19,8 +19,7 @@ public class DeleteQueryBuilder implements QueryAnnotation.QueryBuilder @Override public QueryNode build(SQLConnection connection, Delete queryAnnotation, Method method, ParameterPair[] parameters) { PlaceholderMapper placeholderMapper = new PlaceholderMapper(parameters); - QueryAnnotation.Validator.requireTableDefinition(method, placeholderMapper); - String table = Table.Util.getFromContext(method, placeholderMapper); + String table = Table.Util.getFromContext(method, parameters); QueryNode node = new DeleteQuery(null, table); if (method.isAnnotationPresent(Where.class)) { diff --git a/core/src/main/java/me/zort/sqllib/mapping/builder/InsertQueryBuilder.java b/core/src/main/java/me/zort/sqllib/mapping/builder/InsertQueryBuilder.java new file mode 100644 index 0000000..1ac0e61 --- /dev/null +++ b/core/src/main/java/me/zort/sqllib/mapping/builder/InsertQueryBuilder.java @@ -0,0 +1,35 @@ +package me.zort.sqllib.mapping.builder; + +import me.zort.sqllib.SQLDatabaseConnection; +import me.zort.sqllib.api.SQLConnection; +import me.zort.sqllib.internal.query.InsertQuery; +import me.zort.sqllib.internal.query.QueryNode; +import me.zort.sqllib.mapping.PlaceholderMapper; +import me.zort.sqllib.mapping.QueryAnnotation; +import me.zort.sqllib.mapping.annotation.Insert; +import me.zort.sqllib.mapping.annotation.Table; +import me.zort.sqllib.util.ParameterPair; + +import java.lang.reflect.Method; + +public class InsertQueryBuilder implements QueryAnnotation.QueryBuilder { + @Override + public QueryNode build(SQLConnection connection, Insert queryAnnotation, Method method, ParameterPair[] parameters) { + if (!(connection instanceof SQLDatabaseConnection)) + throw new IllegalArgumentException("The connection must be a SQLDatabaseConnection"); + + String table = Table.Util.getFromContext(method, parameters); + InsertQuery query = ((SQLDatabaseConnection) connection).insert(); + query.into(table, queryAnnotation.cols()); + + PlaceholderMapper mapper = new PlaceholderMapper(parameters); + + String[] vals = queryAnnotation.vals(); + for (int i = 0; i < vals.length; i++) { + vals[i] = mapper.assignValues(vals[i]); + } + + query.values((Object[]) vals); + return query; + } +} diff --git a/core/src/main/java/me/zort/sqllib/mapping/builder/SaveQueryBuilder.java b/core/src/main/java/me/zort/sqllib/mapping/builder/SaveQueryBuilder.java index 0a55038..86c1187 100644 --- a/core/src/main/java/me/zort/sqllib/mapping/builder/SaveQueryBuilder.java +++ b/core/src/main/java/me/zort/sqllib/mapping/builder/SaveQueryBuilder.java @@ -5,7 +5,6 @@ import me.zort.sqllib.api.SQLConnection; import me.zort.sqllib.internal.query.QueryNode; import me.zort.sqllib.internal.query.UpsertQuery; -import me.zort.sqllib.mapping.PlaceholderMapper; import me.zort.sqllib.mapping.QueryAnnotation; import me.zort.sqllib.mapping.annotation.Save; import me.zort.sqllib.mapping.annotation.Table; @@ -19,9 +18,7 @@ public QueryNode build(SQLConnection connection, Save queryAnnotation, Method if (!(connection instanceof SQLDatabaseConnectionImpl)) throw new IllegalArgumentException("The connection must be an instance of SQLDatabaseConnectionImpl"); - PlaceholderMapper placeholderMapper = new PlaceholderMapper(parameters); - QueryAnnotation.Validator.requireTableDefinition(method, placeholderMapper); - String table = Table.Util.getFromContext(method, placeholderMapper); + String table = Table.Util.getFromContext(method, parameters); UpsertQuery query = ((SQLDatabaseConnectionImpl) connection).save(getSaveableObject(parameters)); query.table(table); diff --git a/core/src/main/java/me/zort/sqllib/mapping/builder/SelectQueryBuilder.java b/core/src/main/java/me/zort/sqllib/mapping/builder/SelectQueryBuilder.java index 8228550..1b86da2 100644 --- a/core/src/main/java/me/zort/sqllib/mapping/builder/SelectQueryBuilder.java +++ b/core/src/main/java/me/zort/sqllib/mapping/builder/SelectQueryBuilder.java @@ -21,8 +21,7 @@ public class SelectQueryBuilder implements QueryAnnotation.QueryBuilder