From 36865fa1ffad280a21f558bd259410e0da03ce09 Mon Sep 17 00:00:00 2001 From: ZorT <67344817+ZorTik@users.noreply.github.com> Date: Sun, 14 May 2023 22:02:48 +0200 Subject: [PATCH 1/2] Added: * JavaDocs * ExpirableEntriesCacheManager.java implementation * toString(), equals(), hashCode() implementations in QueryNode.java * SQLConnectionBuilder#withCacheManager * TestCase3.java Changed: * Optimized some imports * DefaultNamingStrategy.java is now SymbolSeparatedNamingStrategy.java * SymbolSeparatedNamingStrategy.java now has option to specify symbol and case type * TestCase1#test6_Pool now prints SQLException stack trace --- .../zort/sqllib/api/cache/CacheManager.java | 31 +++++++++++- .../sqllib/api/options/NamingStrategy.java | 5 ++ core/build.gradle | 1 + .../me/zort/sqllib/SQLConnectionBuilder.java | 35 ++++++++----- .../sqllib/SQLDatabaseConnectionImpl.java | 14 +++--- .../me/zort/sqllib/SQLDatabaseOptions.java | 4 +- .../cache/ExpirableEntriesCacheManager.java | 24 +++++++-- .../internal/impl/DefaultNamingStrategy.java | 29 ----------- .../sqllib/internal/query/QueryDetails.java | 5 ++ .../zort/sqllib/internal/query/QueryNode.java | 15 ++++++ .../naming/SymbolSeparatedNamingStrategy.java | 50 +++++++++++++++++++ .../java/me/zort/sqllib/test/TestCase1.java | 2 +- .../java/me/zort/sqllib/test/TestCase3.java | 28 +++++++++++ 13 files changed, 186 insertions(+), 57 deletions(-) delete mode 100644 core/src/main/java/me/zort/sqllib/internal/impl/DefaultNamingStrategy.java create mode 100644 core/src/main/java/me/zort/sqllib/naming/SymbolSeparatedNamingStrategy.java create mode 100644 src/test/java/me/zort/sqllib/test/TestCase3.java diff --git a/api/src/main/java/me/zort/sqllib/api/cache/CacheManager.java b/api/src/main/java/me/zort/sqllib/api/cache/CacheManager.java index 74d5e5c..c0bef44 100644 --- a/api/src/main/java/me/zort/sqllib/api/cache/CacheManager.java +++ b/api/src/main/java/me/zort/sqllib/api/cache/CacheManager.java @@ -5,16 +5,43 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +/** + * A cache manager that caches query results. This is used + * primarly in {@link me.zort.sqllib.api.SQLConnection} instances + * that hold a cache manager. + * + * @author ZorTik + */ public interface CacheManager { + /** + * Sets a query result to the cache. + * If the query is already cached, it should be overwritten. + * + * @param query Query that was executed + * @param result Result + */ void set(@NotNull Query query, @NotNull QueryResult result); + + /** + * Returns a query result from the cache, or null if + * the query is not cached. + * + * @param query Query that was executed + * @param isExec Whether the query is an exec (no ResultSet) query + * @return The nullable query result + */ @Nullable QueryResult get(@NotNull Query query, boolean isExec); + /** + * Returns a cache manager that does not cache anything. + * + * @return The cache manager + */ static CacheManager noCache() { return new CacheManager() { @Override - public void set(@NotNull Query query, @NotNull QueryResult result) { - } + public void set(@NotNull Query query, @NotNull QueryResult result) {} @Override public @Nullable QueryResult get(@NotNull Query query, boolean isExec) { return null; diff --git a/api/src/main/java/me/zort/sqllib/api/options/NamingStrategy.java b/api/src/main/java/me/zort/sqllib/api/options/NamingStrategy.java index 050d514..c5f91b3 100644 --- a/api/src/main/java/me/zort/sqllib/api/options/NamingStrategy.java +++ b/api/src/main/java/me/zort/sqllib/api/options/NamingStrategy.java @@ -1,5 +1,10 @@ package me.zort.sqllib.api.options; +/** + * A naming strategy that converts field names to column names. + * + * @author ZorTik + */ public interface NamingStrategy { String fieldNameToColumn(String str); diff --git a/core/build.gradle b/core/build.gradle index ef00bcd..1d5fa69 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -14,6 +14,7 @@ dependencies { implementation project(":shared") implementation group: 'org.jetbrains', name: 'annotations', version: '20.1.0' implementation 'com.google.code.gson:gson:2.9.0' + implementation 'com.google.guava:guava:31.0-jre' compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' } \ No newline at end of file diff --git a/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java b/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java index dcb5b73..92d460c 100644 --- a/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java +++ b/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java @@ -4,6 +4,7 @@ import me.zort.sqllib.api.ISQLConnectionBuilder; import me.zort.sqllib.api.ISQLDatabaseOptions; import me.zort.sqllib.api.SQLEndpoint; +import me.zort.sqllib.api.cache.CacheManager; import me.zort.sqllib.internal.Constants; import me.zort.sqllib.internal.exception.SQLDriverNotFoundException; import me.zort.sqllib.internal.exception.SQLEndpointNotValidException; @@ -34,7 +35,7 @@ public final class SQLConnectionBuilder implements ISQLConnectionBuilder(); diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java index a97013e..cc5ca30 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java @@ -7,7 +7,7 @@ import me.zort.sqllib.api.ISQLDatabaseOptions; import me.zort.sqllib.api.options.NamingStrategy; import me.zort.sqllib.internal.Defaults; -import me.zort.sqllib.internal.impl.DefaultNamingStrategy; +import me.zort.sqllib.naming.SymbolSeparatedNamingStrategy; import org.jetbrains.annotations.NotNull; @AllArgsConstructor @@ -18,7 +18,7 @@ public final class SQLDatabaseOptions implements ISQLDatabaseOptions { private boolean autoReconnect = true; private boolean debug = false; private boolean logSqlErrors = true; - private transient NamingStrategy namingStrategy = new DefaultNamingStrategy(); + private transient NamingStrategy namingStrategy = SQLDatabaseConnectionImpl.DEFAULT_NAMING_STRATEGY; private transient Gson gson = Defaults.DEFAULT_GSON; /** diff --git a/core/src/main/java/me/zort/sqllib/cache/ExpirableEntriesCacheManager.java b/core/src/main/java/me/zort/sqllib/cache/ExpirableEntriesCacheManager.java index 7c5ea90..17e6765 100644 --- a/core/src/main/java/me/zort/sqllib/cache/ExpirableEntriesCacheManager.java +++ b/core/src/main/java/me/zort/sqllib/cache/ExpirableEntriesCacheManager.java @@ -1,20 +1,38 @@ package me.zort.sqllib.cache; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import me.zort.sqllib.api.Query; import me.zort.sqllib.api.cache.CacheManager; import me.zort.sqllib.api.data.QueryResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.concurrent.TimeUnit; + public class ExpirableEntriesCacheManager implements CacheManager { + + private final Cache cache; + + /** + * Creates a new cache manager with provided expiration duration + * in milliseconds. + * + * @param expirationDuration The expiration duration + */ + public ExpirableEntriesCacheManager(long expirationDuration) { + cache = CacheBuilder.newBuilder() + .expireAfterWrite(expirationDuration, TimeUnit.MILLISECONDS) + .build(); + } + @Override public void set(@NotNull Query query, @NotNull QueryResult result) { - // TODO: Implement + cache.put(query, result); } @Override public @Nullable QueryResult get(@NotNull Query query, boolean isExec) { - // TODO: Implement - return null; + return cache.getIfPresent(query); } } diff --git a/core/src/main/java/me/zort/sqllib/internal/impl/DefaultNamingStrategy.java b/core/src/main/java/me/zort/sqllib/internal/impl/DefaultNamingStrategy.java deleted file mode 100644 index 0d960f3..0000000 --- a/core/src/main/java/me/zort/sqllib/internal/impl/DefaultNamingStrategy.java +++ /dev/null @@ -1,29 +0,0 @@ -package me.zort.sqllib.internal.impl; - -import me.zort.sqllib.api.options.NamingStrategy; - -public class DefaultNamingStrategy implements NamingStrategy { - - @Override - public String fieldNameToColumn(String str) { - if(str.isEmpty()) return ""; - - char[] chars = str.toCharArray(); - StringBuilder sb = new StringBuilder(); - int index = 0; - for(char c : chars) { - if(Character.isUpperCase(c) - && index > 0 - && !Character.isUpperCase(chars[index - 1]) - && (index == chars.length - 1 || !Character.isUpperCase(chars[index + 1])) - ) { - sb.append('_'); - } - - sb.append(Character.toLowerCase(c)); - index++; - } - return sb.toString(); - } - -} diff --git a/core/src/main/java/me/zort/sqllib/internal/query/QueryDetails.java b/core/src/main/java/me/zort/sqllib/internal/query/QueryDetails.java index 765b684..4769e51 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/QueryDetails.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/QueryDetails.java @@ -1,5 +1,6 @@ package me.zort.sqllib.internal.query; +import com.google.gson.Gson; import lombok.*; import me.zort.sqllib.SQLConnectionRegistry; import me.zort.sqllib.util.Pair; @@ -129,6 +130,10 @@ public int length() { return queryStr.length(); } + public String toString() { + return "QueryDetails{str=" + queryStr + ", values=" + new Gson().toJson(values) + "}"; + } + @RequiredArgsConstructor public static class Builder { diff --git a/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java b/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java index 8269301..1a5cfda 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java @@ -194,4 +194,19 @@ private void debug(String message) { } } + public String toString() { + return "QueryNode{details=" + buildQueryDetails().toString() + "}"; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof QueryNode)) return false; + QueryNode other = (QueryNode) obj; + return toString().equals(other.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } } diff --git a/core/src/main/java/me/zort/sqllib/naming/SymbolSeparatedNamingStrategy.java b/core/src/main/java/me/zort/sqllib/naming/SymbolSeparatedNamingStrategy.java new file mode 100644 index 0000000..bec16d8 --- /dev/null +++ b/core/src/main/java/me/zort/sqllib/naming/SymbolSeparatedNamingStrategy.java @@ -0,0 +1,50 @@ +package me.zort.sqllib.naming; + +import lombok.AllArgsConstructor; +import me.zort.sqllib.api.options.NamingStrategy; + +/** + * A naming strategy that converts field names to column names. + * This naming strategy converts names as snake_case. + * + * @author ZorTik + */ +@AllArgsConstructor +public class SymbolSeparatedNamingStrategy implements NamingStrategy { + + private final char symbol; + private final boolean upperCase; + + /** + * Creates a naming strategy with provided symbol that + * converts names to lower case. + * + * @param symbol The symbol to separate words with. + */ + public SymbolSeparatedNamingStrategy(char symbol) { + this(symbol, false); + } + + @Override + public String fieldNameToColumn(String str) { + if(str.isEmpty()) return ""; + + char[] chars = str.toCharArray(); + StringBuilder sb = new StringBuilder(); + int index = 0; + for(char c : chars) { + if(Character.isUpperCase(c) + && index > 0 + && !Character.isUpperCase(chars[index - 1]) + && (index == chars.length - 1 || !Character.isUpperCase(chars[index + 1])) + ) { + sb.append(symbol); + } + + sb.append(upperCase ? Character.toUpperCase(c) : Character.toLowerCase(c)); + index++; + } + return sb.toString(); + } + +} diff --git a/src/test/java/me/zort/sqllib/test/TestCase1.java b/src/test/java/me/zort/sqllib/test/TestCase1.java index 7d9278a..7ed03c8 100644 --- a/src/test/java/me/zort/sqllib/test/TestCase1.java +++ b/src/test/java/me/zort/sqllib/test/TestCase1.java @@ -165,7 +165,7 @@ public void test6_Pool() { try(SQLDatabaseConnection connection = pool.getResource()) { connection.exec(() -> "xcbnxcvkjonmcvxikjno"); } catch(SQLException e) { - throw new RuntimeException(e); + e.printStackTrace(); } assertEquals(1, pool.errorCount()); assertEquals(0, pool.size()); diff --git a/src/test/java/me/zort/sqllib/test/TestCase3.java b/src/test/java/me/zort/sqllib/test/TestCase3.java new file mode 100644 index 0000000..c17c96e --- /dev/null +++ b/src/test/java/me/zort/sqllib/test/TestCase3.java @@ -0,0 +1,28 @@ +package me.zort.sqllib.test; + +import lombok.extern.log4j.Log4j2; +import me.zort.sqllib.naming.SymbolSeparatedNamingStrategy; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Log4j2 +@EnabledOnOs(value = {OS.LINUX, OS.WINDOWS}) +@TestMethodOrder(MethodOrderer.MethodName.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestCase3 { // Smaller independent modules tests + + @Test + public void testSymbolSeparatedStrategy() { + SymbolSeparatedNamingStrategy strategy = new SymbolSeparatedNamingStrategy('_'); + assertEquals("test", strategy.fieldNameToColumn("test")); + assertEquals("test_two", strategy.fieldNameToColumn("testTwo")); + assertEquals("this_is_third_test", strategy.fieldNameToColumn("thisIsThirdTest")); + } + +} From c973d9f879151e29a5734668b2ef57cb7f20083c Mon Sep 17 00:00:00 2001 From: ZorT <67344817+ZorTik@users.noreply.github.com> Date: Sun, 14 May 2023 22:11:56 +0200 Subject: [PATCH 2/2] Code quality Changed: * Moved come constants to Defaults.java * Deleted Constants.java * Optimized imports --- .../main/java/me/zort/sqllib/SQLConnectionBuilder.java | 3 +-- .../java/me/zort/sqllib/SQLDatabaseConnectionImpl.java | 10 +++++----- .../main/java/me/zort/sqllib/internal/Constants.java | 7 ------- .../main/java/me/zort/sqllib/internal/Defaults.java | 8 ++++++++ 4 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 core/src/main/java/me/zort/sqllib/internal/Constants.java diff --git a/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java b/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java index 92d460c..ab0dd80 100644 --- a/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java +++ b/core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java @@ -5,7 +5,6 @@ import me.zort.sqllib.api.ISQLDatabaseOptions; import me.zort.sqllib.api.SQLEndpoint; import me.zort.sqllib.api.cache.CacheManager; -import me.zort.sqllib.internal.Constants; import me.zort.sqllib.internal.exception.SQLDriverNotFoundException; import me.zort.sqllib.internal.exception.SQLEndpointNotValidException; import me.zort.sqllib.internal.factory.SQLConnectionFactory; @@ -96,7 +95,7 @@ public SQLConnectionBuilder(@Nullable SQLEndpoint endpoint) { private @NotNull SQLDatabaseConnection buildConnection(@Nullable String driver, @Nullable ISQLDatabaseOptions options) { Objects.requireNonNull(endpoint, "Endpoint must be set!"); Objects.requireNonNull(jdbc); - if (driver == null) driver = Constants.DEFAULT_DRIVER; + if (driver == null) driver = SQLDatabaseConnectionImpl.DEFAULT_DRIVER; SQLConnectionFactory connectionFactory = new LocalConnectionFactory(driver); return jdbc.contains("jdbc:sqlite") ? new SQLiteDatabaseConnectionImpl(connectionFactory, options) diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java index a89467c..4d6face 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java @@ -23,7 +23,6 @@ import me.zort.sqllib.internal.factory.SQLConnectionFactory; import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver; import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver; -import me.zort.sqllib.naming.SymbolSeparatedNamingStrategy; import me.zort.sqllib.internal.impl.DefaultObjectMapper; import me.zort.sqllib.internal.impl.QueryResultImpl; import me.zort.sqllib.mapping.DefaultStatementMappingFactory; @@ -61,10 +60,11 @@ public class SQLDatabaseConnectionImpl extends PooledSQLDatabaseConnection { // --***-- Default Constants --***-- - public static final boolean DEFAULT_AUTO_RECONNECT = true; - public static final boolean DEFAULT_DEBUG = false; - public static final boolean DEFAULT_LOG_SQL_ERRORS = true; - public static final NamingStrategy DEFAULT_NAMING_STRATEGY = new SymbolSeparatedNamingStrategy('_'); + public static final String DEFAULT_DRIVER = Defaults.DEFAULT_DRIVER; + public static final boolean DEFAULT_AUTO_RECONNECT = Defaults.DEFAULT_AUTO_RECONNECT; + public static final boolean DEFAULT_DEBUG = Defaults.DEFAULT_DEBUG; + public static final boolean DEFAULT_LOG_SQL_ERRORS = Defaults.DEFAULT_LOG_SQL_ERRORS; + public static final NamingStrategy DEFAULT_NAMING_STRATEGY = Defaults.DEFAULT_NAMING_STRATEGY; public static final Gson DEFAULT_GSON = Defaults.DEFAULT_GSON; // --***-- Options & Utilities --***-- diff --git a/core/src/main/java/me/zort/sqllib/internal/Constants.java b/core/src/main/java/me/zort/sqllib/internal/Constants.java deleted file mode 100644 index c8d0814..0000000 --- a/core/src/main/java/me/zort/sqllib/internal/Constants.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.zort.sqllib.internal; - -public final class Constants { - - public static final String DEFAULT_DRIVER = "com.mysql.jdbc.Driver"; - -} diff --git a/core/src/main/java/me/zort/sqllib/internal/Defaults.java b/core/src/main/java/me/zort/sqllib/internal/Defaults.java index 7b45ff1..6beafd7 100644 --- a/core/src/main/java/me/zort/sqllib/internal/Defaults.java +++ b/core/src/main/java/me/zort/sqllib/internal/Defaults.java @@ -2,9 +2,17 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import me.zort.sqllib.api.options.NamingStrategy; +import me.zort.sqllib.naming.SymbolSeparatedNamingStrategy; public final class Defaults { + public static final String DEFAULT_DRIVER = "com.mysql.jdbc.Driver"; + public static final boolean DEFAULT_AUTO_RECONNECT = true; + public static final boolean DEFAULT_DEBUG = false; + public static final boolean DEFAULT_LOG_SQL_ERRORS = true; + public static final NamingStrategy DEFAULT_NAMING_STRATEGY = new SymbolSeparatedNamingStrategy('_'); + public static final Gson DEFAULT_GSON = new GsonBuilder() .enableComplexMapKeySerialization() .create();