From a9e2a9d39c9fdb453b4e7d7926e29d7aeca3017b Mon Sep 17 00:00:00 2001 From: ZorTik Date: Sun, 11 Feb 2024 15:39:45 +0100 Subject: [PATCH 1/3] Support for raw prepared queries --- .../zort/sqllib/internal/query/QueryNode.java | 33 +++++++++++++++++++ .../java/me/zort/sqllib/test/TestCase1.java | 14 +++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java b/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java index 45959d0..6758953 100644 --- a/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java +++ b/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java @@ -48,6 +48,39 @@ public QueryNode(@Nullable P parent, List> initial, int priority) { this.details = new ConcurrentHashMap<>(); } + /** + * Creates a new QueryNode from a query string in PreparedStatement format and + * parameters to replace question marks in the query. This is useful if there is no + * other way to create a query than using raw SQL details. + *

+ * Example: + *
+   * Query query = QueryNode.fromRawQuery("SELECT * FROM table WHERE id = ?", 1);
+   * 
+ * + * @param query + * @param params + * @return + */ + public static QueryNode fromRawQuery(String query, Object... params) { + return new QueryNode<>(null, Collections.emptyList(), QueryPriority.GENERAL) { + @Override + public QueryDetails buildQueryDetails() { + Map values = new HashMap<>(); + String preparedStr; + int index = 0; + while (true) { + preparedStr = query.replaceFirst("\\?", String.format("", index)); + if (preparedStr.equals(query)) { + break; + } + values.put("val_" + index, params[index]); + } + return new QueryDetails(preparedStr, values); + } + }; + } + /** * Builds the query string with placeholders containing values * for passing into PreparedStatement. diff --git a/src/test/java/me/zort/sqllib/test/TestCase1.java b/src/test/java/me/zort/sqllib/test/TestCase1.java index 271da00..6538003 100644 --- a/src/test/java/me/zort/sqllib/test/TestCase1.java +++ b/src/test/java/me/zort/sqllib/test/TestCase1.java @@ -5,6 +5,8 @@ import me.zort.sqllib.SQLConnectionBuilder; import me.zort.sqllib.internal.annotation.NullableField; import me.zort.sqllib.internal.annotation.PrimaryKey; +import me.zort.sqllib.internal.query.QueryDetails; +import me.zort.sqllib.internal.query.QueryNode; import me.zort.sqllib.pool.SQLConnectionPool; import me.zort.sqllib.SQLDatabaseConnection; import me.zort.sqllib.SQLDatabaseOptions; @@ -14,6 +16,7 @@ import me.zort.sqllib.api.provider.Select; import me.zort.sqllib.internal.impl.DefaultSQLEndpoint; import me.zort.sqllib.transaction.TransactionFlow; +import me.zort.sqllib.util.Pair; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configurator; import org.junit.jupiter.api.*; @@ -196,7 +199,16 @@ public void test6_Transactions() { } @Test - public void test7_Close() { + public void test7_RawNode() { + String raw = "SELECT * FROM users WHERE nickname = ?"; + QueryNode query = QueryNode.fromRawQuery(raw, "User1"); + Pair preparedQuery = query.toPreparedQuery(); + assertEquals(raw, preparedQuery.getFirst()); + assertArrayEquals(new Object[]{"User1"}, preparedQuery.getSecond()); + } + + @Test + public void test8_Close() { System.out.println("Closing connection..."); connection.disconnect(); System.out.println("Connection closed"); From 4fcf15b71833b375e7a57c27a286a3e83045ec72 Mon Sep 17 00:00:00 2001 From: ZorTik Date: Sun, 11 Feb 2024 15:45:11 +0100 Subject: [PATCH 2/3] Fix --- .../main/java/me/zort/sqllib/internal/query/QueryNode.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java b/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java index 6758953..3cb30c6 100644 --- a/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java +++ b/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java @@ -67,11 +67,12 @@ public static QueryNode fromRawQuery(String query, Object... params) { @Override public QueryDetails buildQueryDetails() { Map values = new HashMap<>(); - String preparedStr; + String preparedStr = query; int index = 0; while (true) { - preparedStr = query.replaceFirst("\\?", String.format("", index)); - if (preparedStr.equals(query)) { + final String before = preparedStr; + preparedStr = before.replaceFirst("\\?", String.format("", index)); + if (preparedStr.equals(before)) { break; } values.put("val_" + index, params[index]); From 10c1363216ce89e608a0e297fe5915ff261019c6 Mon Sep 17 00:00:00 2001 From: ZorTik Date: Sun, 11 Feb 2024 16:14:12 +0100 Subject: [PATCH 3/3] queryRaw --- .../me/zort/sqllib/SQLDatabaseConnection.java | 6 ++++ .../sqllib/SQLDatabaseConnectionImpl.java | 35 +++++++++++++++++-- .../zort/sqllib/internal/query/QueryNode.java | 2 ++ .../java/me/zort/sqllib/test/TestCase1.java | 19 ++++++++-- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java b/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java index c395c93..41911e7 100644 --- a/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java +++ b/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java @@ -1,5 +1,6 @@ package me.zort.sqllib; +import com.google.common.annotations.Beta; import lombok.Getter; import me.zort.sqllib.api.DefsVals; import me.zort.sqllib.api.ObjectMapper; @@ -25,6 +26,7 @@ import java.io.Closeable; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -141,6 +143,10 @@ public SQLDatabaseConnection(final @NotNull SQLConnectionFactory connectionFacto public abstract QueryRowsResult query(String query); + @Beta + @Nullable + public abstract ResultSet queryRaw(Query query) throws SQLException; + /** * Executes given query and returns execution result. * This result does not contain any rows. If you want to diff --git a/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java b/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java index 8e574f8..6060fcd 100644 --- a/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java +++ b/asql-core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java @@ -1,5 +1,6 @@ package me.zort.sqllib; +import com.google.common.annotations.Beta; import com.google.gson.Gson; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -32,6 +33,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.sql.rowset.CachedRowSet; +import javax.sql.rowset.RowSetProvider; import java.sql.*; import java.util.Objects; import java.util.Optional; @@ -274,11 +277,14 @@ public QueryRowsResult query(final @NotNull String query) { @NotNull QueryRowsResult query(final @NotNull Query query, boolean isRetry) { Objects.requireNonNull(query); - if (!handleAutoReconnect()) + if (!handleAutoReconnect()) { return new QueryRowsResult<>(false, "Cannot connect to database!"); + } QueryResult cachedResult = cacheManager.get(query, false); - if (cachedResult instanceof QueryRowsResult) return (QueryRowsResult) cachedResult; + if (cachedResult instanceof QueryRowsResult) { + return (QueryRowsResult) cachedResult; + } try (PreparedStatement stmt = buildStatement(query); ResultSet resultSet = stmt.executeQuery()) { @@ -310,6 +316,31 @@ QueryRowsResult query(final @NotNull Query query, boolean isRetry) { } } + /** + * Executes given query and returns raw ResultSet. + * Please note that this function is not recommended to be frequently used and is provided + * as is as feature. + * + * @param query Query to use for building query string. + * @return ResultSet object or null if there was an error in connection. + * @throws SQLException If there was an error while executing query. + */ + @Beta + @Nullable + public ResultSet queryRaw(Query query) throws SQLException { + Objects.requireNonNull(query); + if (!handleAutoReconnect()) { + return null; + } + try (PreparedStatement stmt = buildStatement(query); + ResultSet resultSet = stmt.executeQuery()) { + // Create in-memory cached result set + CachedRowSet cachedResultSet = RowSetProvider.newFactory().createCachedRowSet(); + cachedResultSet.populate(resultSet); + return cachedResultSet; + } + } + /** * Executes given query and returns execution result. * This result does not contain any rows. If you want to diff --git a/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java b/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java index 3cb30c6..abd9f23 100644 --- a/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java +++ b/asql-core/src/main/java/me/zort/sqllib/internal/query/QueryNode.java @@ -1,5 +1,6 @@ package me.zort.sqllib.internal.query; +import com.google.common.annotations.Beta; import lombok.Getter; import me.zort.sqllib.SQLDatabaseConnection; import me.zort.sqllib.SQLDatabaseConnectionImpl; @@ -62,6 +63,7 @@ public QueryNode(@Nullable P parent, List> initial, int priority) { * @param params * @return */ + @Beta public static QueryNode fromRawQuery(String query, Object... params) { return new QueryNode<>(null, Collections.emptyList(), QueryPriority.GENERAL) { @Override diff --git a/src/test/java/me/zort/sqllib/test/TestCase1.java b/src/test/java/me/zort/sqllib/test/TestCase1.java index 6538003..f7bfbdb 100644 --- a/src/test/java/me/zort/sqllib/test/TestCase1.java +++ b/src/test/java/me/zort/sqllib/test/TestCase1.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; import me.zort.sqllib.SQLConnectionBuilder; +import me.zort.sqllib.api.Query; import me.zort.sqllib.internal.annotation.NullableField; import me.zort.sqllib.internal.annotation.PrimaryKey; import me.zort.sqllib.internal.query.QueryDetails; @@ -23,6 +24,7 @@ import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; +import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; @@ -200,7 +202,7 @@ public void test6_Transactions() { @Test public void test7_RawNode() { - String raw = "SELECT * FROM users WHERE nickname = ?"; + String raw = "SELECT * FROM " + table + " WHERE nickname = ?"; QueryNode query = QueryNode.fromRawQuery(raw, "User1"); Pair preparedQuery = query.toPreparedQuery(); assertEquals(raw, preparedQuery.getFirst()); @@ -208,7 +210,20 @@ public void test7_RawNode() { } @Test - public void test8_Close() { + public void test8_RawQuery() { + Query query = QueryNode.fromRawQuery("SELECT * FROM " + table + " WHERE nickname = ?", "User1"); + try { + ResultSet result = connection.queryRaw(query); + assertNotNull(result); + assertTrue(result.next()); + assertEquals("User1", result.getString("nickname")); + } catch (SQLException e) { + fail(e); + } + } + + @Test + public void test9_Close() { System.out.println("Closing connection..."); connection.disconnect(); System.out.println("Connection closed");