Skip to content

Commit 59fac88

Browse files
committed
Experimental: Proxy mapping
1 parent e87e305 commit 59fac88

30 files changed

Lines changed: 737 additions & 30 deletions

api/src/main/java/me/zort/sqllib/api/StatementFactory.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,22 @@
44
import java.sql.SQLException;
55
import java.sql.Statement;
66

7+
/**
8+
* The StatementFactory is responsible for creating new Statements for
9+
* the given connection.
10+
*
11+
* @param <T> The Statement type.
12+
* @author ZorTik
13+
*/
714
public interface StatementFactory<T extends Statement> {
815

16+
/**
17+
* Prepares the statement for executing in {@link SQLConnection}.
18+
*
19+
* @param connection The connection to use.
20+
* @return The prepared statement.
21+
* @throws SQLException If an error occurs while preparing.
22+
*/
923
T prepare(Connection connection) throws SQLException;
1024

1125
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package me.zort.sqllib.api;
2+
3+
import me.zort.sqllib.api.data.QueryResult;
4+
import org.jetbrains.annotations.Nullable;
5+
6+
import java.lang.reflect.Method;
7+
8+
/**
9+
* Represents an abstract proxy mapping that is used for execution of
10+
* queries using mapped methods in a proxy instance.
11+
*
12+
* @param <T> The type of the proxy instance.
13+
* @author ZorTik
14+
*/
15+
public interface StatementMapping<T> {
16+
17+
/**
18+
* Executes query based on invoked method with invoked args from
19+
* proxy instance. MapTo is used as type of entity to be mapped in the
20+
* request. If there is no mapping required, MapTo can be null.
21+
*
22+
* @param method The method that was executed in the proxy.
23+
* @param args The arguments passed.
24+
* @param mapTo The type of entity that needs to be mapped.
25+
* @return The QueryResult of the executed query.
26+
*/
27+
QueryResult executeQuery(Method method, Object[] args, @Nullable Class<?> mapTo);
28+
29+
/**
30+
* Checks if the method is eligible for mapping. If this returns false,
31+
* the method is executed in proxy mapping as normal method, eg. does not
32+
* return any mapping results.
33+
*
34+
* @param method The method that was executed in the proxy.
35+
* @return True if the method is eligible for mapping, or false.
36+
*/
37+
boolean isMappingMethod(Method method);
38+
39+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package me.zort.sqllib.api;
2+
3+
/**
4+
* The StatementMappingFactory is responsible for creating new StatementMapping
5+
* for defined interfaces.
6+
*
7+
* @author ZorTik
8+
*/
9+
public interface StatementMappingFactory {
10+
11+
/**
12+
* Creates a new StatementMapping for the given interface class that
13+
* is responsible for handling that specific interface.
14+
*
15+
* @param interfaceClass The interface class
16+
* @param connection The connection to use.
17+
* @return The StatementMapping.
18+
* @param <T> The interface class type.
19+
*/
20+
<T> StatementMapping<T> create(Class<T> interfaceClass, SQLConnection connection);
21+
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package me.zort.sqllib.api;
2+
3+
import me.zort.sqllib.api.data.QueryResult;
4+
5+
import java.lang.reflect.Method;
6+
7+
/**
8+
* Result adapter used for handling operations between {@link SQLConnection}
9+
* and {@link StatementMapping}.
10+
*
11+
* @author ZorTik
12+
*/
13+
public interface StatementMappingResultAdapter {
14+
15+
/**
16+
* Adapts invoked {@link StatementMapping} method QueryResult to
17+
* the final result that can be passed to proxy instance.
18+
*
19+
* @param method The invoked proxy method.
20+
* @param result The QueryResult of the invoked method.
21+
* @return The adapted result.
22+
*/
23+
Object adaptResult(Method method, QueryResult result);
24+
25+
/**
26+
* Retrieves type of entity that needs to be mapped in the request
27+
* to be passed in adaptResult as type in QueryResult.
28+
*
29+
* @param method The invoked proxy method.
30+
* @return The type of entity that needs to be mapped.
31+
*/
32+
Class<?> retrieveResultType(Method method);
33+
34+
}

core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@
1818

1919
public class SQLConnectionBuilder {
2020

21-
public static SQLConnectionBuilder of(String address,
22-
int port,
23-
String database,
24-
String username,
25-
String password) {
21+
public static SQLConnectionBuilder of(String address, int port, String database, String username, String password) {
2622
return of(new DefaultSQLEndpoint(address + ":" + port, database, username, password));
2723
}
2824

@@ -47,6 +43,10 @@ public static SQLConnectionBuilder of(SQLEndpoint endpoint) {
4743
private String jdbc;
4844
private String driver = null;
4945

46+
public SQLConnectionBuilder(String address, int port, String database, String username, String password) {
47+
this(new DefaultSQLEndpoint(address + ":" + port, database, username, password));
48+
}
49+
5050
public SQLConnectionBuilder() {
5151
this(null);
5252
}

core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import me.zort.sqllib.api.data.Row;
99
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
1010
import me.zort.sqllib.internal.query.*;
11+
import org.jetbrains.annotations.ApiStatus;
1112
import org.jetbrains.annotations.Nullable;
1213

1314
import java.sql.Connection;
@@ -32,6 +33,9 @@ public SQLDatabaseConnection(SQLConnectionFactory connectionFactory) {
3233
SQLConnectionPool.register(this);
3334
}
3435

36+
@ApiStatus.Experimental
37+
public abstract <T> T createMapping(Class<T> mappingInterface);
38+
3539
/**
3640
* Saves this mapping object into database using upsert query.
3741
* <p>

core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import com.google.gson.Gson;
44
import lombok.*;
5-
import me.zort.sqllib.api.ObjectMapper;
6-
import me.zort.sqllib.api.Query;
7-
import me.zort.sqllib.api.StatementFactory;
5+
import me.zort.sqllib.api.*;
86
import me.zort.sqllib.api.data.QueryResult;
97
import me.zort.sqllib.api.data.QueryRowsResult;
108
import me.zort.sqllib.api.data.Row;
@@ -19,8 +17,11 @@
1917
import me.zort.sqllib.internal.impl.QueryResultImpl;
2018
import me.zort.sqllib.internal.query.*;
2119
import me.zort.sqllib.internal.query.part.SetStatement;
20+
import me.zort.sqllib.mapping.DefaultResultAdapter;
21+
import me.zort.sqllib.mapping.DefaultStatementMappingFactory;
2222
import me.zort.sqllib.util.Pair;
2323
import me.zort.sqllib.util.Validator;
24+
import org.jetbrains.annotations.ApiStatus;
2425
import org.jetbrains.annotations.NotNull;
2526
import org.jetbrains.annotations.Nullable;
2627

@@ -49,6 +50,10 @@ public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection {
4950

5051
@Getter
5152
private final SQLDatabaseOptions options;
53+
@ApiStatus.Experimental
54+
private final transient StatementMappingFactory mappingFactory;
55+
@ApiStatus.Experimental
56+
private final transient StatementMappingResultAdapter mappingResultAdapter;
5257
private transient ObjectMapper objectMapper;
5358

5459
/**
@@ -81,6 +86,8 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab
8186

8287
this.options = options;
8388
this.objectMapper = new DefaultObjectMapper(this);
89+
this.mappingFactory = new DefaultStatementMappingFactory();
90+
this.mappingResultAdapter = new DefaultResultAdapter();
8491

8592
// Default backup value resolvers.
8693
registerBackupValueResolver(new LinkedOneFieldResolver());
@@ -97,6 +104,27 @@ public void setObjectMapper(@NotNull ObjectMapper objectMapper) {
97104
this.objectMapper = Objects.requireNonNull(objectMapper, "Object mapper cannot be null!");
98105
}
99106

107+
@SuppressWarnings("unchecked")
108+
@ApiStatus.Experimental
109+
public <T> T createMapping(Class<T> mappingInterface) {
110+
StatementMapping<T> statementMapping = mappingFactory.create(mappingInterface, this);
111+
return (T) Proxy.newProxyInstance(mappingInterface.getClassLoader(),
112+
new Class[]{mappingInterface}, (proxy, method, args) -> {
113+
114+
// Allow invokation from interfaces or abstract classes only.
115+
Class<?> declaringClass = method.getDeclaringClass();
116+
if ((declaringClass.isInterface() || Modifier.isAbstract(declaringClass.getModifiers()))
117+
&& statementMapping.isMappingMethod(method)) {
118+
// Prepare and execute query based on invoked method.
119+
QueryResult result = statementMapping.executeQuery(method, args, mappingResultAdapter.retrieveResultType(method));
120+
// Adapt QueryResult to method return type.
121+
return mappingResultAdapter.adaptResult(method, result);
122+
}
123+
124+
return method.invoke(this, args);
125+
});
126+
}
127+
100128
/**
101129
* @see SQLDatabaseConnection#save(String, Object)
102130
*/
@@ -106,20 +134,24 @@ public QueryResult save(String table, Object obj) { // by default, it creates an
106134
if(defsValsPair == null) {
107135
return new QueryResultImpl(false);
108136
}
137+
109138
String[] defs = defsValsPair.getFirst();
110139
UnknownValueWrapper[] vals = defsValsPair.getSecond();
111140

112141
UpsertQuery upsert = upsert().into(table, defs);
113142
for(UnknownValueWrapper wrapper : vals) {
114143
upsert.appendVal(wrapper.getObject());
115144
}
145+
116146
SetStatement<InsertQuery> setStmt = upsert.onDuplicateKey();
117147
for(int i = 0; i < defs.length; i++) {
118148
setStmt.and(defs[i], vals[i].getObject());
119149
}
150+
120151
return setStmt.execute();
121152
}
122153

154+
@SuppressWarnings("unchecked")
123155
@Nullable
124156
protected Pair<String[], UnknownValueWrapper[]> buildDefsVals(Object obj) {
125157
Objects.requireNonNull(obj);
@@ -168,6 +200,7 @@ protected Pair<String[], UnknownValueWrapper[]> buildDefsVals(Object obj) {
168200
public <T> QueryRowsResult<T> query(Query query, Class<T> typeClass) {
169201
QueryRowsResult<Row> resultRows = query(query.getAncestor());
170202
QueryRowsResult<T> result = new QueryRowsResult<>(resultRows.isSuccessful());
203+
171204
for(Row row : resultRows) {
172205
Optional.ofNullable(objectMapper.assignValues(row, typeClass))
173206
.ifPresent(result::add);

core/src/main/java/me/zort/sqllib/internal/query/Conditional.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import java.util.ArrayList;
77

8-
public interface Conditional<P extends QueryNode<?> & Conditional<P>> {
8+
public interface Conditional<P extends QueryNode<?> & Conditional<P>> { // P = self
99

1010
default WhereStatement<P> where() {
1111
return where(QueryPriority.CONDITION.getPrior());

core/src/main/java/me/zort/sqllib/internal/query/DeleteQuery.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public class DeleteQuery extends QueryNode<QueryNode<?>> implements Executive, C
1515
@Getter
1616
private final SQLDatabaseConnection connection;
1717

18-
public DeleteQuery(SQLDatabaseConnection connection) {
18+
public DeleteQuery(@Nullable SQLDatabaseConnection connection) {
1919
this(connection, null);
2020
}
2121

22-
public DeleteQuery(SQLDatabaseConnection connection, @Nullable String table) {
22+
public DeleteQuery(@Nullable SQLDatabaseConnection connection, @Nullable String table) {
2323
super(null, new ArrayList<>(), QueryPriority.GENERAL);
2424
this.table = table;
2525
this.connection = connection;

core/src/main/java/me/zort/sqllib/internal/query/InsertQuery.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ public class InsertQuery extends QueryNode<QueryNode<?>> implements Executive, C
2222
@Getter
2323
private final SQLDatabaseConnection connection;
2424

25-
public InsertQuery(SQLDatabaseConnection connection) {
25+
public InsertQuery(@Nullable SQLDatabaseConnection connection) {
2626
this(connection, null);
2727
}
2828

29-
public InsertQuery(SQLDatabaseConnection connection, @Nullable String table) {
29+
public InsertQuery(@Nullable SQLDatabaseConnection connection, @Nullable String table) {
3030
super(null, new ArrayList<>(), QueryPriority.GENERAL);
3131
this.table = table;
3232
this.connection = connection;

0 commit comments

Comments
 (0)