Skip to content

Commit

Permalink
Feature/fstorage (#1)
Browse files Browse the repository at this point in the history
* MongoDB & MariaDB Stateless Field Filtering Storage impl
  • Loading branch information
casperwtf authored Oct 30, 2024
1 parent 35a0f4e commit c875df9
Show file tree
Hide file tree
Showing 13 changed files with 1,540 additions and 270 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -227,5 +227,11 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.16</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
49 changes: 49 additions & 0 deletions src/main/java/wtf/casper/storageapi/Filter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package wtf.casper.storageapi;

import java.util.ArrayList;
import java.util.List;

public record Filter(String key, Object value, FilterType filterType, SortingType sortingType, Type type) {
public static Filter of(String key, Object value, FilterType filterType, SortingType sortingType, Type type) {
return new Filter(key, value, filterType, sortingType, type);
}

public static Filter of(String key, Object value, FilterType filterType, SortingType sortingType) {
return new Filter(key, value, filterType, sortingType, Type.AND);
}

public static Filter of(String key, Object value, FilterType filterType) {
return new Filter(key, value, filterType, SortingType.NONE, Type.AND);
}

public enum Type {
AND,
OR
}

/**
* Groups filters into a list of lists, where each list is a group of filters that are connected by OR
* [AND, OR, AND, AND, OR, AND] -> [[AND] OR [AND, AND] OR [AND]]
* @param filters the filters to group
* @return the grouped filters
*/
public static List<List<Filter>> group(Filter... filters) {
List<List<Filter>> groups = new ArrayList<>();
groups.add(new ArrayList<>());
for (Filter filter : filters) {
List<Filter> lastGroup = groups.get(groups.size() - 1);
if (filter.type() == Type.AND) {
lastGroup.add(filter);
continue;
}

if (!lastGroup.isEmpty()) {
groups.add(new ArrayList<>());
lastGroup = groups.get(groups.size() - 1);
}
lastGroup.add(filter);
}

return groups;
}
}
252 changes: 252 additions & 0 deletions src/main/java/wtf/casper/storageapi/StatelessFieldStorage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package wtf.casper.storageapi;

import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import wtf.casper.storageapi.misc.ConstructableValue;
import wtf.casper.storageapi.misc.KeyValue;
import wtf.casper.storageapi.utils.ReflectionUtil;
import wtf.casper.storageapi.utils.StorageAPIConstants;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

public interface StatelessFieldStorage<K, V> {

/**
* @param field the field to search for.
* @param value the value to search for.
* @return a future that will complete with a collection of all values that match the given field and value.
*/
default CompletableFuture<Collection<V>> get(final String field, final Object value) {
return get(field, value, FilterType.EQUALS, SortingType.NONE);
}

/**
* @param field the field to search for.
* @param value the value to search for.
* @param filterType the filter type to use.
* @param sortingType the sorting type to use.
* @return a future that will complete with a collection of all values that match the given field and value.
*/
default CompletableFuture<Collection<V>> get(final String field, final Object value, final FilterType filterType, final SortingType sortingType) {
return get(Filter.of(field, value, filterType, sortingType));
};

/**
* @param filters the filters to use.
* @return a future that will complete with a collection of all value that match the given filters.
*/
default CompletableFuture<Collection<V>> get(Filter... filters) {
return get(-1, filters);
};

/**
* @param limit the limit of values to return.
* @param filters the filters to use.
* @return a future that will complete with a collection of all value that match the given filters.
*/
CompletableFuture<Collection<V>> get(int limit, Filter... filters);

/**
* @param key the key to search for.
* @return a future that will complete with the value that matches the given key.
* The value may be null if the key is not found.
*/
CompletableFuture<V> get(final K key);

/**
* @param key the key to search for.
* @return a future that will complete with the value that matches the given key or a generated value if not found.
*/
default CompletableFuture<V> getOrDefault(final K key) {
return get(key).thenApply((v) -> {

if (v != null) {
return v;
}

if (this instanceof ConstructableValue<?, ?>) {
v = ((ConstructableValue<K, V>) this).constructValue(key);
if (v == null) {
throw new RuntimeException("Failed to create default value for V with key " + key + ". " +
"Please create a constructor in V for only the key");
}
return v;
}

if (this instanceof KeyValue<?, ?>) {
KeyValue<K, V> keyValueGetter = (KeyValue<K, V>) this;
try {
return ReflectionUtil.createInstance(keyValueGetter.value(), key);
} catch (final Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to create default value for V with key " + key + ". " +
"Please create a constructor in V for only the key.", e);
}
}

try {
if (getClass().getGenericSuperclass() instanceof ParameterizedType parameterizedType) {
Type type = parameterizedType.getActualTypeArguments()[1];
Class<V> aClass = (Class<V>) Class.forName(type.getTypeName());
return ReflectionUtil.createInstance(aClass, key);
}

throw new RuntimeException("Failed to create default value for V with key " + key + ". " +
"Please create a constructor in V for only the key.");

} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to create default value for V with key " + key + ". " +
"Please create a constructor in V for only the key.");
}
});
}

/**
* @param field the field to search for.
* @param value the value to search for.
* @return a future that will complete with the first value that matches the given field and value.
*/
default CompletableFuture<V> getFirst(final String field, final Object value) {
return getFirst(field, value, FilterType.EQUALS);
}

/**
* @param field the field to search for.
* @param value the value to search for.
* @param filterType the filter type to use.
* @return a future that will complete with the first value that matches the given field and value.
*/
default CompletableFuture<V> getFirst(final String field, final Object value, FilterType filterType) {
return get(1, Filter.of(field, value, filterType, SortingType.NONE)).thenApply((values) -> {
if (values.isEmpty()) {
return null;
}

return values.iterator().next();
});
};


/**
* @param value the value to save.
*/
CompletableFuture<Void> save(final V value);

/**
* @param values the values to save.
*/
default CompletableFuture<Void> saveAll(final Collection<V> values) {
return CompletableFuture.runAsync(() -> values.forEach(v -> save(v).join()), StorageAPIConstants.DB_THREAD_POOL);
}

/**
* @param key the key to remove.
*/
CompletableFuture<Void> remove(final V key);

/**
* Writes the storage to disk.
*/
CompletableFuture<Void> write();

/**
* Deletes the storage from disk.
*/
CompletableFuture<Void> deleteAll();

/**
* Closes the storage/storage connection.
*/
default CompletableFuture<Void> close() {
return CompletableFuture.completedFuture(null);
}

/**
* @param field the field to search for.
* @param value the value to search for.
* @return a future that will complete with a boolean that represents whether the storage contains a value that matches the given field and value.
*/
CompletableFuture<Boolean> contains(final String field, final Object value);

/**
* @param storage the storage to migrate from. The data will be copied from the given storage to this storage.
* @return a future that will complete with a boolean that represents whether the migration was successful.
*/
default CompletableFuture<Boolean> migrate(final StatelessFieldStorage<K, V> storage) {
return CompletableFuture.supplyAsync(() -> {
storage.allValues().thenAccept((values) -> values.forEach(v -> save(v).join())).join();
return true;
}, StorageAPIConstants.DB_THREAD_POOL);
}

/**
* @param oldStorageSupplier supplier to provide the old storage
* @param config the config
* @param path the path to the storage
* @return a future that will complete with a boolean that represents whether the migration was successful.
*/
default CompletableFuture<Boolean> migrateFrom(Supplier<StatelessFieldStorage<K, V>> oldStorageSupplier, YamlDocument config, String path) {
return CompletableFuture.supplyAsync(() -> {
if (config == null) return false;
Section section = config.getSection(path);
if (section == null) return false;
if (!section.getBoolean("migrate", false)) return false;
section.set("migrate", false);
try {
config.save();
} catch (IOException e) {
e.printStackTrace();
}
// storage that we are migrating to the new storage
StatelessFieldStorage<K, V> oldStorage = oldStorageSupplier.get();
try {
this.migrate(oldStorage).join();
return true;
} catch (Exception e) {
return false;
}
}, StorageAPIConstants.DB_THREAD_POOL);
}

/**
* @return a future that will complete with a collection of all values in the storage.
*/
CompletableFuture<Collection<V>> allValues();

/**
* @param field the field to search for.
* @param sortingType the sorting type to use.
* @return a future that will complete with a collection of all values in the storage that match the given field and value.
*/
default CompletableFuture<Collection<V>> allValues(String field, SortingType sortingType) {
return CompletableFuture.supplyAsync(() -> {
Collection<V> values = allValues().join();
if (values.isEmpty()) {
return values;
}

// Sort the values.
return sortingType.sort(values, field);
}, StorageAPIConstants.DB_THREAD_POOL);
}

/**
* Adds an index to the storage.
* @param field the field to add an index for.
* @return a future that will complete when the index has been added.
*/
CompletableFuture<Void> addIndex(String field);

/**
* Removes an index from the storage.
* @param field the field to remove the index for.
* @return a future that will complete when the index has been removed.
*/
CompletableFuture<Void> removeIndex(String field);
}
13 changes: 13 additions & 0 deletions src/main/java/wtf/casper/storageapi/StorageType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package wtf.casper.storageapi;

/*
* TODO:
* - cubrid
* - db2
* - derby
* - firebird
* - h2
* - hsqldb
* - oracle
* - postgresql
* - mssql
* - hql (hibernate)
*/
public enum StorageType {
JSON,
SQL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package wtf.casper.storageapi.impl.direct.statelessfstorage;

import wtf.casper.storageapi.Credentials;
import wtf.casper.storageapi.impl.statelessfstorage.StatelessMariaDBFStorage;
import wtf.casper.storageapi.impl.statelessfstorage.StatelessMongoFStorage;
import wtf.casper.storageapi.misc.ConstructableValue;

import java.util.function.Function;

public class DirectStatelessMariaDBFStorage<K, V> extends StatelessMariaDBFStorage<K, V> implements ConstructableValue<K, V> {

private final Function<K, V> function;

public DirectStatelessMariaDBFStorage(Class<K> keyClass, Class<V> valueClass, Credentials credentials, Function<K, V> function) {
super(keyClass, valueClass, credentials);
this.function = function;
}

@Override
public V constructValue(K key) {
return function.apply(key);
}
}
Loading

0 comments on commit c875df9

Please sign in to comment.