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
13 changes: 13 additions & 0 deletions asql-api/src/main/java/me/zort/sqllib/api/DefsVals.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package me.zort.sqllib.api;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.concurrent.atomic.AtomicReference;

@AllArgsConstructor
@Getter
public class DefsVals {
private final String[] defs;
private final AtomicReference<Object>[] vals;
}
31 changes: 30 additions & 1 deletion asql-api/src/main/java/me/zort/sqllib/api/ObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Type;

public interface ObjectMapper {

void registerBackupValueResolver(@NotNull FieldValueResolver resolver);
void registerAdapter(@NotNull Class<?> typeClass, @NotNull TypeAdapter<?> adapter);

<T> T assignValues(Row row, Class<T> typeClass);
<T> T deserializeValues(Row row, Class<T> typeClass);
DefsVals serializeValues(Object obj);

interface FieldValueResolver {
Object obtainValue(SQLConnection connection,
Expand All @@ -20,4 +23,30 @@ Object obtainValue(SQLConnection connection,
String convertedName,
Type type);
}

interface TypeAdapter<T> { // TODO: Tests
/**
* Deserializes value from database to Java value.
* This value is commonly set to field value in corresponding
* object. Don't set the value to the element manually!
*
* @param element Element to deserialize value for.
* @param row Row to deserialize value from.
* @param raw Raw value to deserialize.
* @return Deserialized value that will be set to the element.
*/
T deserialize(AnnotatedElement element, Row row, Object raw);

/**
* Serializes value to be inserted into database.
*
* @param element Element to serialize value for.
* @param value Value to serialize.
* This value should be suitable to the database type,
* so you should always check if the value corresponds to
* the column type, etc.
* @return The serialized value that will be inserted into database.
*/
Object serialize(AnnotatedElement element, Object value);
}
}
31 changes: 16 additions & 15 deletions asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package me.zort.sqllib;

import lombok.AllArgsConstructor;
import lombok.Getter;
import me.zort.sqllib.api.DefsVals;
import me.zort.sqllib.api.ObjectMapper;
import me.zort.sqllib.api.Query;
import me.zort.sqllib.api.SQLConnection;
import me.zort.sqllib.api.cache.CacheManager;
import me.zort.sqllib.api.data.QueryResult;
import me.zort.sqllib.api.data.QueryRowsResult;
import me.zort.sqllib.api.data.Row;
import me.zort.sqllib.api.mapping.StatementMappingFactory;
import me.zort.sqllib.api.mapping.StatementMappingOptions;
import me.zort.sqllib.api.model.SchemaSynchronizer;
import me.zort.sqllib.api.model.TableSchema;
import me.zort.sqllib.api.model.TableSchemaBuilder;
Expand Down Expand Up @@ -71,6 +70,15 @@ public SQLDatabaseConnection(final @NotNull SQLConnectionFactory connectionFacto
@ApiStatus.Experimental
public abstract void setSchemaSynchronizer(SchemaSynchronizer<SQLDatabaseConnection> synchronizer);

/**
* Sets the object mapper to use.
* Object mapper maps queries to objects, as specified in {@link SQLDatabaseConnection#query(Query, Class)}.
*
* @param objectMapper Object mapper to use.
*/
public abstract void setObjectMapper(final @NotNull ObjectMapper objectMapper);
public abstract ObjectMapper getObjectMapper();

public abstract boolean buildEntitySchema(String tableName, Class<?> entityClass);

/**
Expand Down Expand Up @@ -167,8 +175,6 @@ public SQLDatabaseConnection(final @NotNull SQLConnectionFactory connectionFacto

public abstract boolean isTransactionActive();

protected abstract DefsVals buildDefsVals(Object obj);

public abstract boolean isLogSqlErrors();

public abstract boolean isDebug();
Expand Down Expand Up @@ -220,12 +226,14 @@ public SQLDatabaseConnection cacheFor(long millis) {
}

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

public UpsertQuery save(final @NotNull Object obj) {
DefsVals defsVals = buildDefsVals(obj);
DefsVals defsVals = getObjectMapper().serializeValues(obj);
if (defsVals == null) return null;
String[] defs = defsVals.getDefs();
AtomicReference<Object>[] vals = defsVals.getVals();
Expand All @@ -242,7 +250,7 @@ public UpsertQuery save(final @NotNull Object obj) {
}

public QueryResult insert(final @NotNull String table, final @NotNull Object obj) {
DefsVals defsVals = buildDefsVals(obj);
DefsVals defsVals = getObjectMapper().serializeValues(obj);
if (defsVals == null) return new QueryResultImpl(false);

InsertQuery query = insert().into(table, defsVals.getDefs());
Expand Down Expand Up @@ -343,13 +351,6 @@ public interface CodeObserver {
void onNotified(int code);
}

@AllArgsConstructor
@Getter
protected static class DefsVals {
private final String[] defs;
private final AtomicReference<Object>[] vals;
}

public static final class Code {
public static final int CONNECTED = 1;
public static final int CLOSED = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import me.zort.sqllib.api.model.TableSchemaBuilder;
import me.zort.sqllib.api.options.NamingStrategy;
import me.zort.sqllib.internal.Defaults;
import me.zort.sqllib.internal.annotation.JsonField;
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver;
import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver;
Expand All @@ -29,19 +28,13 @@
import me.zort.sqllib.model.builder.EntitySchemaBuilder;
import me.zort.sqllib.pool.PooledSQLDatabaseConnection;
import me.zort.sqllib.transaction.Transaction;
import me.zort.sqllib.util.Validator;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

/**
Expand Down Expand Up @@ -72,6 +65,7 @@ static SQLDatabaseOptions defaultOptions() {

@Getter
private final ISQLDatabaseOptions options;
@Getter
private transient ObjectMapper objectMapper;
private transient CacheManager cacheManager;
@Setter
Expand Down Expand Up @@ -130,6 +124,7 @@ public void registerBackupValueResolver(final @NotNull ObjectMapper.FieldValueRe
*
* @param objectMapper Object mapper to use.
*/
@Override
public void setObjectMapper(final @NotNull ObjectMapper objectMapper) {
this.objectMapper = Objects.requireNonNull(objectMapper, "Object mapper cannot be null!");
}
Expand Down Expand Up @@ -254,7 +249,7 @@ public <T> QueryRowsResult<T> query(final @NotNull Query query, final @NotNull C
QueryRowsResult<T> result = new QueryRowsResult<>(resultRows.isSuccessful());

for (Row row : resultRows) {
Optional.ofNullable(objectMapper.assignValues(row, typeClass))
Optional.ofNullable(objectMapper.deserializeValues(row, typeClass))
.ifPresent(result::add);
}
return result;
Expand Down Expand Up @@ -360,48 +355,6 @@ public QueryResult exec(final @NotNull String query) {
}
}

@SuppressWarnings("unchecked")
@Nullable
protected final DefsVals buildDefsVals(Object obj) {
Objects.requireNonNull(obj);

Class<?> aClass = obj.getClass();

Map<String, Object> fields = new HashMap<>();
for (Field field : aClass.getDeclaredFields()) {

if (Modifier.isTransient(field.getModifiers())) {
// Transient fields are ignored.
continue;
}

try {
field.setAccessible(true);
Object o = field.get(obj);
if (field.isAnnotationPresent(JsonField.class)) {
o = options.getGson().toJson(o);
} else if (Validator.validateAutoIncrement(field) && field.get(obj) == null) {
// If field is PrimaryKey and autoIncrement true and is null,
// We will skip this to use auto increment strategy on SQL server.
continue;
}
fields.put(options.getNamingStrategy().fieldNameToColumn(field.getName()), o);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
// I make entry array for indexing safety.
Map.Entry<String, Object>[] entryArray = fields.entrySet().toArray(new Map.Entry[0]);
String[] defs = new String[entryArray.length];
AtomicReference<Object>[] vals = new AtomicReference[entryArray.length];
for (int i = 0; i < entryArray.length; i++) {
defs[i] = entryArray[i].getKey();
vals[i] = new AtomicReference<>(entryArray[i].getValue());
}
return new DefsVals(defs, vals);
}

@ApiStatus.Experimental
@SneakyThrows(SQLException.class)
public final Transaction beginTransaction() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.zort.sqllib;

import me.zort.sqllib.api.DefsVals;
import me.zort.sqllib.api.ISQLDatabaseOptions;
import me.zort.sqllib.api.Query;
import me.zort.sqllib.api.data.QueryResult;
Expand Down Expand Up @@ -65,7 +66,7 @@ private void setup() {
@NotNull
@Override
public final UpsertQuery save(@NotNull String table, @NotNull Object obj) {
DefsVals defsVals = buildDefsVals(obj);
DefsVals defsVals = getObjectMapper().serializeValues(obj);
if (defsVals == null) throw new IllegalArgumentException("Cannot create save query! (defsVals == null)");
String[] defs = defsVals.getDefs();
AtomicReference<Object>[] vals = defsVals.getVals();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,36 @@
import lombok.AccessLevel;
import lombok.Getter;
import me.zort.sqllib.SQLDatabaseConnectionImpl;
import me.zort.sqllib.api.DefsVals;
import me.zort.sqllib.api.ISQLDatabaseOptions;
import me.zort.sqllib.api.ObjectMapper;
import me.zort.sqllib.api.data.Row;
import me.zort.sqllib.internal.annotation.JsonField;
import me.zort.sqllib.util.Validator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

public class DefaultObjectMapper implements ObjectMapper {

// Resolvers used after no value is found for the field
// in mapped object as backup.
@Getter(AccessLevel.PROTECTED)
private final List<ObjectMapper.FieldValueResolver> backupValueResolvers;
private final Map<Class<?>, ObjectMapper.TypeAdapter<?>> typeAdapters;
private final SQLDatabaseConnectionImpl connectionWrapper;

public DefaultObjectMapper(SQLDatabaseConnectionImpl connectionWrapper) {
this.backupValueResolvers = new CopyOnWriteArrayList<>();
this.typeAdapters = new ConcurrentHashMap<>();
this.connectionWrapper = connectionWrapper;
}

Expand All @@ -32,8 +42,13 @@ public void registerBackupValueResolver(@NotNull FieldValueResolver resolver) {
this.backupValueResolvers.add(resolver);
}

@Override
public void registerAdapter(@NotNull Class<?> typeClass, @NotNull TypeAdapter<?> adapter) {
this.typeAdapters.put(typeClass, adapter);
}

@Nullable
public <T> T assignValues(Row row, Class<T> typeClass) {
public <T> T deserializeValues(Row row, Class<T> typeClass) {
T instance = null;
try {
try {
Expand Down Expand Up @@ -113,6 +128,11 @@ private Object buildElementValue(AnnotatedElement element, Row row) {
debug(String.format("Cannot find column for class %s target %s (%s)", declaringClass.getName(), name, converted));
return null;
}
} else {
TypeAdapter<?> typeAdapter = typeAdapters.get(type.getClass());
if (typeAdapter != null) {
return typeAdapter.deserialize(element, row, obj);
}
}
if (element.isAnnotationPresent(JsonField.class) && obj instanceof String) {
String jsonString = (String) obj;
Expand All @@ -123,6 +143,51 @@ private Object buildElementValue(AnnotatedElement element, Row row) {
}
}

@Override
public DefsVals serializeValues(Object obj) {
Objects.requireNonNull(obj);

Class<?> aClass = obj.getClass();

Map<String, Object> fields = new HashMap<>();
for (Field field : aClass.getDeclaredFields()) {

if (Modifier.isTransient(field.getModifiers())) {
// Transient fields are ignored.
continue;
}

ISQLDatabaseOptions options = connectionWrapper.getOptions();

try {
field.setAccessible(true);
Object o = field.get(obj);
if (typeAdapters.containsKey(field.getType())) {
o = typeAdapters.get(field.getType()).serialize(field, o);
} else if (field.isAnnotationPresent(JsonField.class)) {
o = options.getGson().toJson(o);
} else if (Validator.validateAutoIncrement(field) && field.get(obj) == null) {
// If field is PrimaryKey and autoIncrement true and is null,
// We will skip this to use auto increment strategy on SQL server.
continue;
}
fields.put(options.getNamingStrategy().fieldNameToColumn(field.getName()), o);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
// I make entry array for indexing safety.
Map.Entry<String, Object>[] entryArray = fields.entrySet().toArray(new Map.Entry[0]);
String[] defs = new String[entryArray.length];
AtomicReference<Object>[] vals = new AtomicReference[entryArray.length];
for (int i = 0; i < entryArray.length; i++) {
defs[i] = entryArray[i].getKey();
vals[i] = new AtomicReference<>(entryArray[i].getValue());
}
return new DefsVals(defs, vals);
}

private void debug(String message) {
connectionWrapper.debug(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static WhereStatement<?> build(Conditional<?> parent, Where annotation, P
case BT:
case LT:
try {
int number = Integer.parseInt(value);
long number = Long.parseLong(value);
if (condition.type().equals(Condition.Type.BT)) {
where.bt(condition.column(), number);
} else {
Expand Down