mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-09 21:29:12 +01:00
Implemented parameterized array Sql generation
This bit of code allows passing lists of data in parameterized queries, which is useful for defining WHERE x IN (?,?,?,?) in a dynamic way. - Adds AccessControlTest cases for a valid /v1/query call
This commit is contained in:
parent
048033d531
commit
022a690446
@ -25,6 +25,7 @@ import com.djrapitops.plan.identification.ServerUUID;
|
|||||||
import com.djrapitops.plan.storage.database.SQLDB;
|
import com.djrapitops.plan.storage.database.SQLDB;
|
||||||
import com.djrapitops.plan.storage.database.queries.Query;
|
import com.djrapitops.plan.storage.database.queries.Query;
|
||||||
import com.djrapitops.plan.storage.database.queries.QueryStatement;
|
import com.djrapitops.plan.storage.database.queries.QueryStatement;
|
||||||
|
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||||
import com.djrapitops.plan.storage.database.sql.tables.*;
|
import com.djrapitops.plan.storage.database.sql.tables.*;
|
||||||
import org.apache.commons.text.TextStringBuilder;
|
import org.apache.commons.text.TextStringBuilder;
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ public class ExtensionQueryResultTableDataQuery implements Query<Map<UUID, Exten
|
|||||||
private Query<Map<UUID, ExtensionTabData>> fetchPlayerData() {
|
private Query<Map<UUID, ExtensionTabData>> fetchPlayerData() {
|
||||||
String selectUuids = SELECT + UsersTable.USER_UUID +
|
String selectUuids = SELECT + UsersTable.USER_UUID +
|
||||||
FROM + UsersTable.TABLE_NAME +
|
FROM + UsersTable.TABLE_NAME +
|
||||||
WHERE + UsersTable.ID + " IN (" + new TextStringBuilder().appendWithSeparators(userIds, ",") + ")";
|
WHERE + UsersTable.ID + " IN (" + Sql.nParameters(userIds.size()) + ")";
|
||||||
|
|
||||||
String sql = SELECT +
|
String sql = SELECT +
|
||||||
"v1." + ExtensionPlayerValueTable.USER_UUID + " as uuid," +
|
"v1." + ExtensionPlayerValueTable.USER_UUID + " as uuid," +
|
||||||
@ -104,19 +105,13 @@ public class ExtensionQueryResultTableDataQuery implements Query<Map<UUID, Exten
|
|||||||
AND + "p1." + ExtensionProviderTable.IS_PLAYER_NAME + "=?" +
|
AND + "p1." + ExtensionProviderTable.IS_PLAYER_NAME + "=?" +
|
||||||
AND + "e1." + ExtensionPluginTable.SERVER_UUID + "=?";
|
AND + "e1." + ExtensionPluginTable.SERVER_UUID + "=?";
|
||||||
|
|
||||||
return new QueryStatement<>(sql, 1000) {
|
return db -> db.queryMap(sql, this::extractPlayer, HashMap::new,
|
||||||
@Override
|
userIds,
|
||||||
public void prepare(PreparedStatement statement) throws SQLException {
|
true, // Select only values that should be shown
|
||||||
statement.setBoolean(1, true); // Select only values that should be shown
|
false, // Don't select player_name String values
|
||||||
statement.setBoolean(2, false); // Don't select player_name String values
|
serverUUID)
|
||||||
statement.setString(3, serverUUID.toString());
|
.entrySet().stream()
|
||||||
}
|
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().build()));
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<UUID, ExtensionTabData> processResults(ResultSet set) throws SQLException {
|
|
||||||
return extractDataByPlayer(set);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Query<Map<UUID, ExtensionTabData>> fetchPlayerGroups() {
|
private Query<Map<UUID, ExtensionTabData>> fetchPlayerGroups() {
|
||||||
@ -155,6 +150,12 @@ public class ExtensionQueryResultTableDataQuery implements Query<Map<UUID, Exten
|
|||||||
Map<UUID, ExtensionTabData.Builder> dataByPlayer = new HashMap<>();
|
Map<UUID, ExtensionTabData.Builder> dataByPlayer = new HashMap<>();
|
||||||
|
|
||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
|
extractPlayer(set, dataByPlayer);
|
||||||
|
}
|
||||||
|
return dataByPlayer.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extractPlayer(ResultSet set, Map<UUID, ExtensionTabData.Builder> dataByPlayer) throws SQLException {
|
||||||
UUID playerUUID = UUID.fromString(set.getString("uuid"));
|
UUID playerUUID = UUID.fromString(set.getString("uuid"));
|
||||||
ExtensionTabData.Builder data = dataByPlayer.getOrDefault(playerUUID, new ExtensionTabData.Builder(null));
|
ExtensionTabData.Builder data = dataByPlayer.getOrDefault(playerUUID, new ExtensionTabData.Builder(null));
|
||||||
|
|
||||||
@ -163,8 +164,6 @@ public class ExtensionQueryResultTableDataQuery implements Query<Map<UUID, Exten
|
|||||||
|
|
||||||
dataByPlayer.put(playerUUID, data);
|
dataByPlayer.put(playerUUID, data);
|
||||||
}
|
}
|
||||||
return dataByPlayer.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().build()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractAndPutDataTo(ExtensionTabData.Builder extensionTab, ExtensionDescription description, ResultSet set) throws SQLException {
|
private void extractAndPutDataTo(ExtensionTabData.Builder extensionTab, ExtensionDescription description, ResultSet set) throws SQLException {
|
||||||
String groupValue = set.getString("group_value");
|
String groupValue = set.getString("group_value");
|
||||||
|
@ -21,6 +21,7 @@ import com.djrapitops.plan.identification.ServerUUID;
|
|||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class QueryParameterSetter {
|
public class QueryParameterSetter {
|
||||||
@ -30,10 +31,22 @@ public class QueryParameterSetter {
|
|||||||
public static void setParameters(PreparedStatement statement, Object... parameters) throws SQLException {
|
public static void setParameters(PreparedStatement statement, Object... parameters) throws SQLException {
|
||||||
int index = 1;
|
int index = 1;
|
||||||
for (Object parameter : parameters) {
|
for (Object parameter : parameters) {
|
||||||
|
if (parameter instanceof Object[]) {
|
||||||
|
for (Object arrayParameter : ((Object[]) parameter)) {
|
||||||
|
setParameter(statement, index, arrayParameter);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
} else if (parameter instanceof Collection) {
|
||||||
|
for (Object collectionParameter : ((Collection<?>) parameter)) {
|
||||||
|
setParameter(statement, index, collectionParameter);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
setParameter(statement, index, parameter);
|
setParameter(statement, index, parameter);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void setParameter(PreparedStatement statement, int index, Object parameter) throws SQLException {
|
private static void setParameter(PreparedStatement statement, int index, Object parameter) throws SQLException {
|
||||||
if (parameter == null) {
|
if (parameter == null) {
|
||||||
|
@ -16,10 +16,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.storage.database.sql.building;
|
package com.djrapitops.plan.storage.database.sql.building;
|
||||||
|
|
||||||
|
import org.apache.commons.text.TextStringBuilder;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicate String reducing utility class for SQL language Strings.
|
* Duplicate String reducing utility class for SQL language Strings.
|
||||||
@ -57,6 +60,12 @@ public abstract class Sql {
|
|||||||
private static final String MAX = "MAX(";
|
private static final String MAX = "MAX(";
|
||||||
private static final String VARCHAR = "varchar(";
|
private static final String VARCHAR = "varchar(";
|
||||||
|
|
||||||
|
public static String nParameters(int n) {
|
||||||
|
return new TextStringBuilder()
|
||||||
|
.appendWithSeparators(IntStream.range(0, n).mapToObj(i -> "?").iterator(), ",")
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String varchar(int length) {
|
public static String varchar(int length) {
|
||||||
return VARCHAR + length + ')';
|
return VARCHAR + length + ')';
|
||||||
}
|
}
|
||||||
|
@ -205,6 +205,7 @@ class AccessControlTest {
|
|||||||
"/query,200",
|
"/query,200",
|
||||||
"/v1/filters,200",
|
"/v1/filters,200",
|
||||||
"/v1/query,400",
|
"/v1/query,400",
|
||||||
|
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,200",
|
||||||
"/v1/errors,200",
|
"/v1/errors,200",
|
||||||
"/errors,200",
|
"/errors,200",
|
||||||
"/v1/network/listServers,200",
|
"/v1/network/listServers,200",
|
||||||
@ -278,6 +279,7 @@ class AccessControlTest {
|
|||||||
"/query,200",
|
"/query,200",
|
||||||
"/v1/filters,200",
|
"/v1/filters,200",
|
||||||
"/v1/query,400",
|
"/v1/query,400",
|
||||||
|
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,200",
|
||||||
"/v1/errors,403",
|
"/v1/errors,403",
|
||||||
"/errors,403",
|
"/errors,403",
|
||||||
"/v1/network/listServers,403",
|
"/v1/network/listServers,403",
|
||||||
@ -351,6 +353,7 @@ class AccessControlTest {
|
|||||||
"/query,403",
|
"/query,403",
|
||||||
"/v1/filters,403",
|
"/v1/filters,403",
|
||||||
"/v1/query,403",
|
"/v1/query,403",
|
||||||
|
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,403",
|
||||||
"/v1/errors,403",
|
"/v1/errors,403",
|
||||||
"/errors,403",
|
"/errors,403",
|
||||||
"/v1/network/listServers,403",
|
"/v1/network/listServers,403",
|
||||||
@ -423,6 +426,7 @@ class AccessControlTest {
|
|||||||
"/v1/players,403",
|
"/v1/players,403",
|
||||||
"/query,403",
|
"/query,403",
|
||||||
"/v1/filters,403",
|
"/v1/filters,403",
|
||||||
|
"/v1/query?q=%5B%5D&view=%7B%22afterDate%22%3A%2224%2F10%2F2022%22%2C%22afterTime%22%3A%2218%3A21%22%2C%22beforeDate%22%3A%2223%2F11%2F2022%22%2C%22beforeTime%22%3A%2217%3A21%22%2C%22servers%22%3A%5B%0A%7B%22serverUUID%22%3A%22" + TestConstants.SERVER_UUID_STRING + "%22%2C%22serverName%22%3A%22" + TestConstants.SERVER_NAME + "%22%2C%22proxy%22%3Afalse%7D%5D%7D,403",
|
||||||
"/v1/query,403",
|
"/v1/query,403",
|
||||||
"/v1/network/listServers,403",
|
"/v1/network/listServers,403",
|
||||||
"/v1/network/serverOptions,403",
|
"/v1/network/serverOptions,403",
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.storage.database.sql.building;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.RepeatedTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import utilities.RandomData;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author AuroraLS3
|
||||||
|
*/
|
||||||
|
class SqlTest {
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "Generating {0} parameters generates {1}")
|
||||||
|
@CsvSource(delimiter = ';', value = {
|
||||||
|
"1;?",
|
||||||
|
"2;?,?",
|
||||||
|
"5;?,?,?,?,?"
|
||||||
|
})
|
||||||
|
void nParametersReturns(Integer n, String expected) {
|
||||||
|
String result = Sql.nParameters(n);
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Generating 0 parameters generates '' (empty string)")
|
||||||
|
void zeroParametersReturnsEmpty() {
|
||||||
|
String result = Sql.nParameters(0);
|
||||||
|
assertEquals("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RepeatedTest(10)
|
||||||
|
@DisplayName("Generating n (random) parameters generates n '?' characters and n-1 ',' characters")
|
||||||
|
void randomParametersReturns() {
|
||||||
|
int n = RandomData.randomInt(10, 50);
|
||||||
|
|
||||||
|
String result = Sql.nParameters(n);
|
||||||
|
int questions = 0;
|
||||||
|
int commas = 0;
|
||||||
|
for (char c : result.toCharArray()) {
|
||||||
|
if (c == '?') {
|
||||||
|
questions++;
|
||||||
|
} else if (c == ',') {
|
||||||
|
commas++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(n, questions);
|
||||||
|
assertEquals(n - 1, commas);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user