Skip to content

Commit 37b737d

Browse files
committed
Schema synchronization 1st commit
Added: * Schema builder implementations * Mapping proxy wrappers * Mapping registry * Other minor changes
1 parent 6f59b64 commit 37b737d

15 files changed

Lines changed: 412 additions & 49 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package me.zort.sqllib.api.mapping;
2+
3+
import me.zort.sqllib.api.model.TableSchema;
4+
import me.zort.sqllib.api.options.NamingStrategy;
5+
6+
import java.lang.reflect.InvocationHandler;
7+
import java.util.List;
8+
9+
public interface MappingProxyInstance<T> extends InvocationHandler {
10+
11+
T getProxyInstance();
12+
List<TableSchema> getTableSchemas(NamingStrategy namingStrategy, boolean sqLite);
13+
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package me.zort.sqllib.api.mapping;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Holds mapping proxy instances.
7+
*
8+
* @author ZorTik
9+
*/
10+
public interface StatementMappingRegistry {
11+
12+
void registerProxy(MappingProxyInstance<?> proxyInstance);
13+
List<MappingProxyInstance<?>> getProxyInstances();
14+
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package me.zort.sqllib.api.model;
2+
3+
/**
4+
* Synchronized (updates) table schema (column definitions) in provided source.
5+
*
6+
* @param <S> The target source
7+
*/
8+
public interface SchemaSynchronizer<S> {
9+
10+
/**
11+
* Table synchronization logic in provided source.
12+
* This should update the 'to' schema to be synchronized with the 'from'
13+
* schema.
14+
*
15+
* @param source The source where to apply changes
16+
* @param from The template schema
17+
* @param to The schema to be <u>updated</u> to match 'from' schema
18+
*/
19+
void synchronize(S source, TableSchema from, TableSchema to);
20+
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package me.zort.sqllib.api.model;
2+
3+
public class TableSchema {
4+
5+
private final String table;
6+
private final String[] definitions;
7+
8+
public TableSchema(String table, String[] definitions) {
9+
this.table = table;
10+
this.definitions = definitions;
11+
}
12+
13+
public String getDefinition(int index) {
14+
return definitions[index];
15+
}
16+
17+
public String[] getDefinitions() {
18+
return definitions;
19+
}
20+
21+
public String getTable() {
22+
return table;
23+
}
24+
25+
public int size() {
26+
return definitions.length;
27+
}
28+
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package me.zort.sqllib.api.model;
2+
3+
public interface TableSchemaBuilder {
4+
5+
TableSchema buildTableSchema();
6+
7+
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import me.zort.sqllib.api.data.Row;
1212
import me.zort.sqllib.api.mapping.StatementMappingFactory;
1313
import me.zort.sqllib.api.mapping.StatementMappingOptions;
14+
import me.zort.sqllib.api.mapping.StatementMappingRegistry;
15+
import me.zort.sqllib.api.model.SchemaSynchronizer;
16+
import me.zort.sqllib.api.model.TableSchema;
17+
import me.zort.sqllib.api.model.TableSchemaBuilder;
1418
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
1519
import me.zort.sqllib.internal.impl.QueryResultImpl;
1620
import me.zort.sqllib.internal.query.*;
@@ -52,6 +56,10 @@ public SQLDatabaseConnection(final @NotNull SQLConnectionFactory connectionFacto
5256
* @param mappingFactory Mapping factory to use.
5357
*/
5458
public abstract void setProxyMapping(final @NotNull StatementMappingFactory mappingFactory);
59+
@ApiStatus.Experimental
60+
public abstract void setSchemaSynchronizer(SchemaSynchronizer<SQLDatabaseConnection> synchronizer);
61+
@ApiStatus.Experimental
62+
public abstract StatementMappingRegistry getMappingRegistry();
5563

5664
/**
5765
* @deprecated Use {@link SQLDatabaseConnection#createProxy(Class)} instead.
@@ -93,6 +101,12 @@ public SQLDatabaseConnection(final @NotNull SQLConnectionFactory connectionFacto
93101
public abstract <T> T createProxy(Class<T> mappingInterface);
94102
public abstract <T> T createProxy(Class<T> mappingInterface, @NotNull StatementMappingOptions options);
95103
public abstract boolean buildEntitySchema(String tableName, Class<?> entityClass);
104+
@ApiStatus.Experimental
105+
public abstract void synchronizeModel();
106+
@ApiStatus.Experimental
107+
public abstract void synchronizeModel(TableSchema entitySchema, String table);
108+
@ApiStatus.Experimental
109+
public abstract void synchronizeModel(Class<?> entity, String table);
96110

97111
/**
98112
* Performs new query and returns the result. This result is never null.
@@ -148,6 +162,10 @@ public SQLDatabaseConnection(final @NotNull SQLConnectionFactory connectionFacto
148162
protected abstract DefsVals buildDefsVals(Object obj);
149163
public abstract boolean isLogSqlErrors();
150164
public abstract boolean isDebug();
165+
@ApiStatus.Experimental
166+
public abstract TableSchemaBuilder getSchemaBuilder(String table);
167+
@ApiStatus.Experimental
168+
public abstract SchemaSynchronizer<SQLDatabaseConnection> getSchemaSynchronizer();
151169

152170
public UpsertQuery save(final @NotNull String table, final @NotNull Object obj) {
153171
if(buildDefsVals(obj) == null) throw new IllegalArgumentException("Cannot create save query! (defsVals == null)");

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

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
import me.zort.sqllib.api.data.QueryResult;
1414
import me.zort.sqllib.api.data.QueryRowsResult;
1515
import me.zort.sqllib.api.data.Row;
16+
import me.zort.sqllib.api.mapping.MappingProxyInstance;
1617
import me.zort.sqllib.api.mapping.StatementMappingFactory;
1718
import me.zort.sqllib.api.mapping.StatementMappingOptions;
18-
import me.zort.sqllib.api.mapping.StatementMappingResultAdapter;
19-
import me.zort.sqllib.api.mapping.StatementMappingStrategy;
19+
import me.zort.sqllib.api.mapping.StatementMappingRegistry;
20+
import me.zort.sqllib.api.model.SchemaSynchronizer;
21+
import me.zort.sqllib.api.model.TableSchema;
22+
import me.zort.sqllib.api.model.TableSchemaBuilder;
2023
import me.zort.sqllib.api.options.NamingStrategy;
2124
import me.zort.sqllib.internal.Defaults;
2225
import me.zort.sqllib.internal.annotation.JsonField;
@@ -26,6 +29,11 @@
2629
import me.zort.sqllib.internal.impl.DefaultObjectMapper;
2730
import me.zort.sqllib.internal.impl.QueryResultImpl;
2831
import me.zort.sqllib.mapping.DefaultStatementMappingFactory;
32+
import me.zort.sqllib.mapping.MappingRegistryImpl;
33+
import me.zort.sqllib.mapping.ProxyInstanceImpl;
34+
import me.zort.sqllib.model.DatabaseSchemaBuilder;
35+
import me.zort.sqllib.model.EntitySchemaBuilder;
36+
import me.zort.sqllib.model.SQLSchemaSynchronizer;
2937
import me.zort.sqllib.pool.PooledSQLDatabaseConnection;
3038
import me.zort.sqllib.transaction.Transaction;
3139
import me.zort.sqllib.util.Validator;
@@ -34,12 +42,12 @@
3442
import org.jetbrains.annotations.Nullable;
3543

3644
import java.lang.reflect.Field;
37-
import java.lang.reflect.Method;
3845
import java.lang.reflect.Modifier;
3946
import java.lang.reflect.Proxy;
4047
import java.sql.*;
4148
import java.util.*;
4249
import java.util.concurrent.CopyOnWriteArrayList;
50+
import java.util.concurrent.atomic.AtomicReference;
4351
import java.util.logging.Logger;
4452

4553
import static me.zort.sqllib.util.ExceptionsUtility.runCatching;
@@ -72,6 +80,7 @@ public class SQLDatabaseConnectionImpl extends PooledSQLDatabaseConnection {
7280
@Getter
7381
private final ISQLDatabaseOptions options;
7482
private final transient List<ErrorStateObserver> errorStateHandlers;
83+
private final transient MappingRegistryImpl mappingRegistry;
7584
private transient StatementMappingFactory mappingFactory;
7685
private transient ObjectMapper objectMapper;
7786
private transient CacheManager cacheManager;
@@ -105,6 +114,7 @@ public SQLDatabaseConnectionImpl(final @NotNull SQLConnectionFactory connectionF
105114
this.errorStateHandlers = new CopyOnWriteArrayList<>();
106115
this.transaction = null;
107116
this.logger = Logger.getGlobal();
117+
this.mappingRegistry = new MappingRegistryImpl(this, new SQLSchemaSynchronizer());
108118

109119
enableCaching(CacheManager.noCache());
110120

@@ -166,6 +176,46 @@ public void enableCaching(CacheManager cacheManager) {
166176
this.cacheManager = cacheManager;
167177
}
168178

179+
@ApiStatus.Experimental
180+
@Override
181+
public void synchronizeModel() {
182+
mappingRegistry.getProxyInstances()
183+
.stream().flatMap(i -> i.getTableSchemas(
184+
getOptions().getNamingStrategy(),
185+
this instanceof SQLiteDatabaseConnectionImpl).stream())
186+
.forEach(schema -> synchronizeModel(schema, schema.getTable()));
187+
}
188+
189+
@ApiStatus.Experimental
190+
@Override
191+
public void synchronizeModel(TableSchema entitySchema, String table) {
192+
getSchemaSynchronizer().synchronize(this, entitySchema, getSchemaBuilder(table).buildTableSchema());
193+
}
194+
195+
@ApiStatus.Experimental
196+
@Override
197+
public void synchronizeModel(Class<?> entity, String table) {
198+
synchronizeModel(new EntitySchemaBuilder(table, entity, getOptions().getNamingStrategy(), this instanceof SQLiteDatabaseConnectionImpl).buildTableSchema(), table);
199+
}
200+
201+
@ApiStatus.Experimental
202+
@Override
203+
public void setSchemaSynchronizer(SchemaSynchronizer<SQLDatabaseConnection> synchronizer) {
204+
mappingRegistry.setSynchronizer(synchronizer);
205+
}
206+
207+
@ApiStatus.Experimental
208+
@Override
209+
public SchemaSynchronizer<SQLDatabaseConnection> getSchemaSynchronizer() {
210+
return mappingRegistry.getSynchronizer();
211+
}
212+
213+
@ApiStatus.Experimental
214+
@Override
215+
public StatementMappingRegistry getMappingRegistry() {
216+
return mappingRegistry;
217+
}
218+
169219
/**
170220
* Constructs a mapping proxy based on provided interface.
171221
* The interface should follow rules for creating mapping repositories
@@ -228,37 +278,23 @@ public final <T> T createProxy(final @NotNull Class<T> mappingInterface, final @
228278
Objects.requireNonNull(mappingInterface, "Mapping interface cannot be null!");
229279
Objects.requireNonNull(options, "Options cannot be null!");
230280

231-
StatementMappingStrategy<T> statementMapping = mappingFactory.strategy(mappingInterface, this);
232-
StatementMappingResultAdapter mappingResultAdapter = mappingFactory.resultAdapter();
233-
List<Method> pendingMethods = new CopyOnWriteArrayList<>();
281+
AtomicReference<MappingProxyInstance<T>> instanceReference = new AtomicReference<>();
282+
instanceReference.set(new ProxyInstanceImpl<>((T) Proxy.newProxyInstance(mappingInterface.getClassLoader(),
283+
new Class[]{mappingInterface}, (proxy, method, args) -> instanceReference.get().invoke(proxy, method, args)),
284+
options,
285+
mappingFactory.strategy(mappingInterface, this),
286+
mappingFactory.resultAdapter()));
234287

235-
return (T) Proxy.newProxyInstance(mappingInterface.getClassLoader(),
236-
new Class[]{mappingInterface}, (proxy, method, args) -> {
237-
238-
// Allow invokation from interfaces or abstract classes only.
239-
Class<?> declaringClass = method.getDeclaringClass();
240-
if ((declaringClass.isInterface() || Modifier.isAbstract(declaringClass.getModifiers()))
241-
&& statementMapping.isMappingMethod(method)) {
242-
// Prepare and execute query based on invoked method.
243-
QueryResult result = statementMapping.executeQuery(options, method, args, mappingResultAdapter.retrieveResultType(method));
244-
// Adapt QueryResult to method return type.
245-
return mappingResultAdapter.adaptResult(method, result);
246-
}
247-
248-
// Default methods are invoked normally.
249-
if (declaringClass.isInterface() && method.isDefault()) {
250-
return JVM.getJVM().invokeDefault(declaringClass, proxy, method, args);
251-
}
252-
253-
throw new UnsupportedOperationException("Method " + method.getName() + " is not supported by this mapping repository!");
254-
});
288+
MappingProxyInstance<T> proxyInstanceWrapper = instanceReference.get();
289+
mappingRegistry.registerProxy(proxyInstanceWrapper);
290+
return proxyInstanceWrapper.getProxyInstance();
255291
}
256292

257293
@ApiStatus.Experimental
258294
public final boolean buildEntitySchema(final @NotNull String tableName, final @NotNull Class<?> entityClass) {
259295
Objects.requireNonNull(entityClass, "Entity class cannot be null!");
260296

261-
TableSchemaBuilder converter = new TableSchemaBuilder(this, tableName, entityClass);
297+
EntitySchemaBuilder converter = new EntitySchemaBuilder(tableName, entityClass, options.getNamingStrategy(), this instanceof SQLiteDatabaseConnectionImpl);
262298
String query = converter.buildTableQuery();
263299

264300
return exec(() -> query).isSuccessful();
@@ -518,6 +554,17 @@ public final boolean isTransactionActive() {
518554
return transaction != null && transaction.isActive();
519555
}
520556

557+
@Override
558+
public TableSchemaBuilder getSchemaBuilder(String table) {
559+
return new DatabaseSchemaBuilder(q -> {
560+
try {
561+
return buildStatement(() -> q);
562+
} catch (SQLException e) {
563+
throw new RuntimeException(e);
564+
}
565+
}, table);
566+
}
567+
521568
@SuppressWarnings("all")
522569
private void notifyError(int code) {
523570
errorCount++;

core/src/main/java/me/zort/sqllib/mapping/DefaultStatementMapping.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package me.zort.sqllib.mapping;
22

3-
import lombok.RequiredArgsConstructor;
43
import me.zort.sqllib.SQLDatabaseConnection;
54
import me.zort.sqllib.api.SQLConnection;
5+
import me.zort.sqllib.api.data.QueryResult;
66
import me.zort.sqllib.api.data.QueryRowsResult;
77
import me.zort.sqllib.api.mapping.StatementMappingOptions;
88
import me.zort.sqllib.api.mapping.StatementMappingStrategy;
9-
import me.zort.sqllib.api.data.QueryResult;
109
import me.zort.sqllib.internal.query.QueryNode;
1110
import me.zort.sqllib.internal.query.ResultSetAware;
1211
import me.zort.sqllib.mapping.annotation.Append;
@@ -25,11 +24,14 @@
2524
* @param <T> The type of the proxy instance.
2625
* @author ZorTik
2726
*/
28-
@RequiredArgsConstructor
2927
public class DefaultStatementMapping<T> implements StatementMappingStrategy<T> {
3028

3129
private final SQLConnection connection;
3230

31+
public DefaultStatementMapping(SQLConnection connection) {
32+
this.connection = connection;
33+
}
34+
3335
@SuppressWarnings("unchecked")
3436
@Override
3537
public QueryResult executeQuery(StatementMappingOptions options, Method method, Object[] args, @Nullable Class<?> mapTo) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package me.zort.sqllib.mapping;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import me.zort.sqllib.SQLDatabaseConnection;
6+
import me.zort.sqllib.SQLDatabaseConnectionImpl;
7+
import me.zort.sqllib.SQLiteDatabaseConnectionImpl;
8+
import me.zort.sqllib.api.mapping.MappingProxyInstance;
9+
import me.zort.sqllib.api.mapping.StatementMappingRegistry;
10+
import me.zort.sqllib.api.model.SchemaSynchronizer;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.concurrent.ConcurrentHashMap;
16+
17+
public class MappingRegistryImpl implements StatementMappingRegistry {
18+
19+
private final Map<Class<?>, MappingProxyInstance<?>> proxyWrappers = new ConcurrentHashMap<>();
20+
private final SQLDatabaseConnectionImpl connection;
21+
@Setter
22+
@Getter
23+
private SchemaSynchronizer<SQLDatabaseConnection> synchronizer;
24+
25+
public MappingRegistryImpl(SQLDatabaseConnectionImpl connection, SchemaSynchronizer<SQLDatabaseConnection> synchronizer) {
26+
this.connection = connection;
27+
this.synchronizer = synchronizer;
28+
}
29+
30+
@Override
31+
public void registerProxy(MappingProxyInstance<?> proxyInstance) {
32+
proxyWrappers.put(proxyInstance.getProxyInstance().getClass(), proxyInstance);
33+
}
34+
35+
@Override
36+
public List<MappingProxyInstance<?>> getProxyInstances() {
37+
return new ArrayList<>(proxyWrappers.values());
38+
}
39+
40+
}

0 commit comments

Comments
 (0)