From 90bf807557731376da062fa4266de3562e223a01 Mon Sep 17 00:00:00 2001 From: Hubert Kuliniak Date: Mon, 5 Aug 2024 15:11:22 +0200 Subject: [PATCH] create 'commons-sql' --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +- settings.gradle.kts | 1 + sql/build.gradle.kts | 81 +++++++++++ sql/gradle.properties | 2 + .../mrstudios/commons/sql/SqlConnection.java | 97 +++++++++++++ .../commons/sql/result/SqlEntry.java | 31 +++++ .../commons/sql/result/SqlResult.java | 129 ++++++++++++++++++ .../commons/sql/statement/SqlStatement.java | 114 ++++++++++++++++ .../sql/statement/SqlStatementObject.java | 11 ++ 9 files changed, 469 insertions(+), 3 deletions(-) create mode 100644 sql/build.gradle.kts create mode 100644 sql/gradle.properties create mode 100644 sql/src/main/java/pl/mrstudios/commons/sql/SqlConnection.java create mode 100644 sql/src/main/java/pl/mrstudios/commons/sql/result/SqlEntry.java create mode 100644 sql/src/main/java/pl/mrstudios/commons/sql/result/SqlResult.java create mode 100644 sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatement.java create mode 100644 sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatementObject.java diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5588bcf..b1a2736 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,5 @@ name: 'Bug Report' description: 'Select if you want to report commons issue.' -title: "[BUG] " labels: [ 'bug' ] body: - type: 'dropdown' @@ -10,8 +9,9 @@ body: description: 'With what element do you have problems?' multiple: false options: - - 'Inject' - - 'Reflection' + - 'inject' + - 'reflection' + - 'sql' validations: required: true - type: 'input' diff --git a/settings.gradle.kts b/settings.gradle.kts index 46820f6..e7f1d98 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,3 +14,4 @@ rootProject.name = "commons" module("inject") module("reflection") +module("sql") diff --git a/sql/build.gradle.kts b/sql/build.gradle.kts new file mode 100644 index 0000000..4e0d66d --- /dev/null +++ b/sql/build.gradle.kts @@ -0,0 +1,81 @@ +import com.palantir.gradle.gitversion.VersionDetails +import groovy.lang.Closure +import java.lang.System.getenv + +plugins { + id("java") + id("maven-publish") + id("com.palantir.git-version") version "3.1.0" +} + +val versionDetails: Closure by extra + +project.group = project.parent?.group!! +project.version = project.parent?.version!! + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) +} + +repositories { + mavenCentral() +} + +dependencies { + + /* HikariCP */ + implementation("com.zaxxer:HikariCP:${project.property("hikaricp.version")}") + + /* JetBrains Annotations */ + compileOnly("org.jetbrains:annotations:${project.parent?.property("jetbrains.annotations.version")}") + annotationProcessor("org.jetbrains:annotations:${project.parent?.property("jetbrains.annotations.version")}") + + /* JUnit */ + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.junit.jupiter:junit-jupiter:${project.parent?.property("junit.version")}") + +} + +publishing { + + publications { + create("publish") { + groupId = project.group as String + artifactId = project.name + version = project.version as String + from(components["java"]) + } + } + + repositories { + maven { + url = uri("https://repo.mrstudios.pl/public/") + credentials { + username = getenv("REPOSITORY_USER") + password = getenv("REPOSITORY_PASSWORD") + } + } + } + +} + +tasks { + + withType { + options.encoding = "UTF-8" + } + + test { + useJUnitPlatform() + testLogging { + events("passed") + } + } + + build { + dependsOn(test) + if (versionDetails().branchName == "ver/latest") + finalizedBy(publish) + } + +} \ No newline at end of file diff --git a/sql/gradle.properties b/sql/gradle.properties new file mode 100644 index 0000000..eb84ea2 --- /dev/null +++ b/sql/gradle.properties @@ -0,0 +1,2 @@ +# Dependency Properties +hikaricp.version=5.1.0 \ No newline at end of file diff --git a/sql/src/main/java/pl/mrstudios/commons/sql/SqlConnection.java b/sql/src/main/java/pl/mrstudios/commons/sql/SqlConnection.java new file mode 100644 index 0000000..41f2f22 --- /dev/null +++ b/sql/src/main/java/pl/mrstudios/commons/sql/SqlConnection.java @@ -0,0 +1,97 @@ +package pl.mrstudios.commons.sql; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.jetbrains.annotations.NotNull; +import pl.mrstudios.commons.sql.result.SqlEntry; +import pl.mrstudios.commons.sql.result.SqlResult; +import pl.mrstudios.commons.sql.statement.SqlStatement; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.util.Collection; +import java.util.LinkedList; + +import static java.lang.Class.forName; + +public class SqlConnection { + + private final HikariConfig config; + private HikariDataSource dataSource; + + public SqlConnection( + @NotNull HikariConfig hikariConfig + ) { + this.config = hikariConfig; + this.dataSource = new HikariDataSource(this.config); + } + + @SuppressWarnings("all") + public @NotNull Collection fetch( + @NotNull SqlStatement statement + ) { + + Collection result = new LinkedList<>(); + + if (this.dataSource.isClosed()) + this.dataSource = new HikariDataSource(this.config); + + try ( + Connection connection = this.dataSource.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(statement.query()) + ) { + + statement.prepare(preparedStatement); + try (ResultSet resultSet = preparedStatement.executeQuery()) { + while (resultSet.next()) + result.add(handleReceivedResultSet(resultSet)); + } + + } catch (@NotNull Exception exception) { + throw new RuntimeException("Unable to fetch entries from database due to exception.", exception); + } + + return result; + + } + + @SuppressWarnings("all") + public void execute( + @NotNull SqlStatement statement + ) { + + try ( + Connection connection = this.dataSource.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(statement.query()) + ) { + statement.prepare(preparedStatement); + preparedStatement.execute(); + } catch (@NotNull Exception exception) { + throw new RuntimeException("Unable to execute statement due to exception.", exception); + } + + } + + protected static @NotNull SqlResult handleReceivedResultSet( + @NotNull ResultSet resultSet + ) { + + try { + + SqlResult result = new SqlResult(); + ResultSetMetaData metaData = resultSet.getMetaData(); + + for (int i = 1; i <= metaData.getColumnCount(); i++) + result.add(new SqlEntry(metaData.getColumnName(i), forName(metaData.getColumnClassName(i)), resultSet.getObject(i))); + + return result; + + } catch (@NotNull Exception exception) { + throw new RuntimeException("Unable to handle received result due to exception.", exception); + } + + } + +} diff --git a/sql/src/main/java/pl/mrstudios/commons/sql/result/SqlEntry.java b/sql/src/main/java/pl/mrstudios/commons/sql/result/SqlEntry.java new file mode 100644 index 0000000..85837ab --- /dev/null +++ b/sql/src/main/java/pl/mrstudios/commons/sql/result/SqlEntry.java @@ -0,0 +1,31 @@ +package pl.mrstudios.commons.sql.result; + +import org.jetbrains.annotations.NotNull; + +public record SqlEntry( + @NotNull String key, + @NotNull Class type, + @NotNull Object object +) { + + public @NotNull Long asLong() { + return (Long) this.object; + } + + public @NotNull String asString() { + return (String) this.object; + } + + public @NotNull Integer asInteger() { + return (Integer) this.object; + } + + public @NotNull Double asDouble() { + return (Double) this.object; + } + + public @NotNull Object asObject() { + return this.object; + } + +} diff --git a/sql/src/main/java/pl/mrstudios/commons/sql/result/SqlResult.java b/sql/src/main/java/pl/mrstudios/commons/sql/result/SqlResult.java new file mode 100644 index 0000000..f3a7690 --- /dev/null +++ b/sql/src/main/java/pl/mrstudios/commons/sql/result/SqlResult.java @@ -0,0 +1,129 @@ +package pl.mrstudios.commons.sql.result; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static java.util.List.copyOf; + +public class SqlResult implements Collection { + + private final Collection entries; { + this.entries = new LinkedList<>(); + } + + public @NotNull SqlEntry entry( + @NotNull String key + ) { + return this.entries.stream() + .filter((entry) -> entry.key().equalsIgnoreCase(key)) + .findFirst().orElseThrow(); + } + + public @NotNull Collection entries() { + return copyOf(this.entries); + } + + @Override + public int size() { + return this.entries.size(); + } + + @Override + public boolean isEmpty() { + return this.entries.isEmpty(); + } + + @Override + public boolean contains( + @NotNull Object object + ) { + return this.entries.contains(object); + } + + @Override + public @NotNull Iterator iterator() { + return this.entries.iterator(); + } + + @Override + public void forEach( + @NotNull Consumer action + ) { + this.entries.forEach(action); + } + + @Override + public @NotNull Object[] toArray() { + return this.entries.toArray(); + } + + @Override + public @NotNull T[] toArray( + @NotNull T[] array + ) { + return this.entries.toArray(array); + } + + @Override + public boolean add( + @NotNull SqlEntry sqlEntry + ) { + return this.entries.add(sqlEntry); + } + + @Override + public boolean remove( + @NotNull Object object + ) { + return this.entries.remove(object); + } + + @Override + public boolean containsAll( + @NotNull Collection collection + ) { + return this.entries.containsAll(collection); + } + + @Override + public boolean addAll( + @NotNull Collection collection + ) { + return this.entries.addAll(collection); + } + + @Override + public boolean removeAll( + @NotNull Collection collection + ) { + return this.entries.removeAll(collection); + } + + @Override + public boolean retainAll( + @NotNull Collection collection + ) { + return this.entries.retainAll(collection); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Unable to clear entities due to limited access."); + } + + @Override + public @NotNull Stream stream() { + return this.entries.stream(); + } + + @Override + public @NotNull Stream parallelStream() { + return this.entries.parallelStream(); + } + +} diff --git a/sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatement.java b/sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatement.java new file mode 100644 index 0000000..f3b7e74 --- /dev/null +++ b/sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatement.java @@ -0,0 +1,114 @@ +package pl.mrstudios.commons.sql.statement; + +import org.jetbrains.annotations.NotNull; +import pl.mrstudios.commons.sql.SqlConnection; +import pl.mrstudios.commons.sql.result.SqlResult; + +import java.sql.JDBCType; +import java.sql.PreparedStatement; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static java.sql.JDBCType.*; + +public class SqlStatement { + + private final String query; + private final Map elements; + + protected SqlStatement( + @NotNull String query + ) { + this.query = query; + this.elements = new HashMap<>(); + } + + public @NotNull SqlStatement setString( + @NotNull Integer position, + @NotNull String value + ) { + return this.set(position, VARCHAR, value); + } + + public @NotNull SqlStatement setLongString( + @NotNull Integer position, + @NotNull String value + ) { + return this.set(position, LONGVARCHAR, value); + } + + public @NotNull SqlStatement setInteger( + @NotNull Integer position, + @NotNull Integer value + ) { + return this.set(position, INTEGER, value); + } + + public @NotNull SqlStatement setDouble( + @NotNull Integer position, + @NotNull Double value + ) { + return this.set(position, DOUBLE, value); + } + + public @NotNull SqlStatement setLong( + @NotNull Integer position, + @NotNull Long value + ) { + return this.set(position, BIGINT, value); + } + + public @NotNull SqlStatement setFloat( + @NotNull Integer position, + @NotNull Float value + ) { + return this.set(position, FLOAT, value); + } + + public @NotNull SqlStatement set( + @NotNull Integer position, + @NotNull JDBCType type, + @NotNull Object value + ) { + this.elements.put(position, new SqlStatementObject(position, type, value)); + return this; + } + + public @NotNull String query() { + return this.query; + } + + public void prepare( + @NotNull PreparedStatement preparedStatement + ) { + + this.elements.forEach((position, object) -> { + try { + preparedStatement.setObject(position, object.object(), object.type()); + } catch (@NotNull Exception exception) { + throw new RuntimeException("Unable to prepare statement due to exception.", exception); + } + }); + + } + + public void execute( + @NotNull SqlConnection sqlConnection + ) { + sqlConnection.execute(this); + } + + public @NotNull Collection fetch( + @NotNull SqlConnection sqlConnection + ) { + return sqlConnection.fetch(this); + } + + public static @NotNull SqlStatement createStatement( + @NotNull String query + ) { + return new SqlStatement(query); + } + +} diff --git a/sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatementObject.java b/sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatementObject.java new file mode 100644 index 0000000..b6333c1 --- /dev/null +++ b/sql/src/main/java/pl/mrstudios/commons/sql/statement/SqlStatementObject.java @@ -0,0 +1,11 @@ +package pl.mrstudios.commons.sql.statement; + +import org.jetbrains.annotations.NotNull; + +import java.sql.SQLType; + +record SqlStatementObject( + @NotNull Integer position, + @NotNull SQLType type, + @NotNull Object object +) {}