Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
31 changes: 31 additions & 0 deletions core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* Example:
* <pre>
* &#64;Table("users")
* public interface MyRepository {
* &#64;Select("*")
* &#64;Where(&#64;Where.Condition(column = "firstname", value = "{First Name}"))
* &#64;Limit(1)
* Optional&lt;User&gt; getUser(&#64;Placeholder("First Name") String firstName);
*
* &#64;Select
* List&lt;User&gt; getUsers();
*
* &#64;Delete
* QueryResult deleteUsers();
* }
*
* SQLDatabaseConnection connection = ...;
* MyRepository repository = connection.createGate(MyRepository.class);
*
* Optional&lt;User&gt; user = repository.getUser("John");
* </pre>
*
* @param mappingInterface Interface to create mapping repository for.
* @return Mapping repository.
* @param <T> Type of mapping repository.
*/
@ApiStatus.Experimental
public abstract <T> T createGate(Class<T> mappingInterface);

Expand Down
86 changes: 80 additions & 6 deletions core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
*
* @author ZorTik
*/
@SuppressWarnings("unused")
public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection {

// --***-- Default Constants --***--
Expand Down Expand Up @@ -123,14 +124,38 @@ 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.
* <p>
* Example:
* <pre>
* &#64;Table("users")
* public interface MyRepository {
* &#64;Select("*")
* &#64;Where(&#64;Where.Condition(column = "firstname", value = "{First Name}"))
* &#64;Limit(1)
* Optional&lt;User&gt; getUser(&#64;Placeholder("First Name") String firstName);
*
* &#64;Select
* List&lt;User&gt; getUsers();
*
* &#64;Delete
* QueryResult deleteUsers();
* }
*
* SQLDatabaseConnection connection = ...;
* MyRepository repository = connection.createGate(MyRepository.class);
*
* Optional&lt;User&gt; user = repository.getUser("John");
* </pre>
*
* @param mappingInterface Interface to create mapping repository for.
* @return Mapping repository.
* @param <T> Type of mapping repository.
*/
@SuppressWarnings("unchecked")
@ApiStatus.Experimental
public <T> T createGate(Class<T> mappingInterface) {
public final <T> T createGate(Class<T> mappingInterface) {
Objects.requireNonNull(mappingInterface, "Mapping interface cannot be null!");

StatementMappingStrategy<T> statementMapping = mappingFactory.create(mappingInterface, this);
return (T) Proxy.newProxyInstance(mappingInterface.getClassLoader(),
new Class[]{mappingInterface}, (proxy, method, args) -> {
Expand All @@ -145,15 +170,35 @@ public <T> T createGate(Class<T> 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:
* <p>
* query(Select.of().from("players"), Player.class)
* .stream()
* .map(Player::getNickname)
* .forEach(System.out::println);
* <p>
* 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 <T> Type of objects in result.
*
* @return Collection of row objects.
*/
@Override
public <T> QueryRowsResult<T> query(Query query, Class<T> typeClass) {
Objects.requireNonNull(query);
Objects.requireNonNull(typeClass);

QueryRowsResult<Row> resultRows = query(query.getAncestor());
QueryRowsResult<T> result = new QueryRowsResult<>(resultRows.isSuccessful());

Expand All @@ -165,7 +210,9 @@ public <T> QueryRowsResult<T> query(Query query, Class<T> 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<Row> query(Query query) {
Expand Down Expand Up @@ -200,7 +247,14 @@ public QueryRowsResult<Row> 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()) {
Expand All @@ -216,7 +270,14 @@ public QueryResult exec(Query query) {
}

/**
* @see SQLDatabaseConnection#save(String, Object)
* Saves this mapping object into database using upsert query.
* <p>
* 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.
Expand Down Expand Up @@ -285,6 +346,7 @@ protected Pair<String[], UnknownValueWrapper[]> 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!");
Expand All @@ -296,6 +358,8 @@ private boolean handleAutoReconnect() {
return true;
}

// --***-- Query builders --***--

public SelectQuery select(String... cols) {
return new SelectQuery(this, cols);
}
Expand Down Expand Up @@ -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<PreparedStatement> factory = new DefaultStatementFactory(query);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ public QueryNode(@Nullable P parent, List<QueryNode<?>> initial, int priority) {
this.details = new ConcurrentHashMap<>();
}

/**
* Builds the query string with placeholders containing values
* for passing into PreparedStatement.
* <p>
* Query example: SELECT * FROM table WHERE id = &lt;id&gt;;
* Values example: [AnyId]
*
* @return QueryDetails object.
*/
public abstract QueryDetails buildQueryDetails();

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}

Expand All @@ -66,10 +68,6 @@ public interface QueryBuilder<T extends Annotation> {
}

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);
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/me/zort/sqllib/mapping/annotation/Insert.java
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public class DeleteQueryBuilder implements QueryAnnotation.QueryBuilder<Delete>
@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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Insert> {
@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ public class SelectQueryBuilder implements QueryAnnotation.QueryBuilder<Select>
@Override
public QueryNode<?> build(SQLConnection connection, Select 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 SelectQuery(null, table, queryAnnotation.value().equals("*")
? new ArrayList<>() : Arrays.asList(queryAnnotation.value().replaceAll(" ", "").split(",")));
Expand Down
2 changes: 1 addition & 1 deletion examples/src/main/java/me/zort/sqllib/Example.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public class Example {

public void example() {
SQLDatabaseConnectionImpl connection = SQLConnectionBuilder.of(
SQLDatabaseConnection connection = SQLConnectionBuilder.of(
"localhost",
3306,
"database",
Expand Down
Loading