Convert bulkupdate & search functionality to use PreparedStatements (#972)

This commit is contained in:
Luck 2018-05-05 18:17:48 +01:00
parent c4c98aaabf
commit 7ee9b93365
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
11 changed files with 118 additions and 55 deletions

View File

@ -87,56 +87,35 @@ public final class BulkUpdate {
* *
* @return this query in SQL form * @return this query in SQL form
*/ */
public String buildAsSql() { public PreparedStatementBuilder buildAsSql() {
// DELETE FROM {table} WHERE ... // DELETE FROM {table} WHERE ...
// UPDATE {table} SET ... WHERE ... // UPDATE {table} SET ... WHERE ...
StringBuilder sb = new StringBuilder(); PreparedStatementBuilder builder = new PreparedStatementBuilder();
// add the action // add the action
// (DELETE FROM or UPDATE) // (DELETE FROM or UPDATE)
sb.append(this.action.getAsSql()); this.action.appendSql(builder);
// if there are no constraints, just return without a WHERE clause // if there are no constraints, just return without a WHERE clause
if (this.queries.isEmpty()) { if (this.queries.isEmpty()) {
return sb.append(";").toString(); return builder;
} }
// append constraints // append constraints
sb.append(" WHERE"); builder.append(" WHERE");
for (int i = 0; i < this.queries.size(); i++) { for (int i = 0; i < this.queries.size(); i++) {
Query query = this.queries.get(i); Query query = this.queries.get(i);
sb.append(" "); builder.append(" ");
if (i != 0) { if (i != 0) {
sb.append("AND "); builder.append("AND ");
} }
sb.append(query.getAsSql()); query.appendSql(builder);
} }
return sb.append(";").toString(); return builder;
}
/**
* Utility to appropriately escape a string for use in a query.
*
* @param s the string to escape
* @return an escaped string
*/
public static String escapeStringForSql(String s) {
if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) {
return s.toLowerCase();
}
try {
Integer.parseInt(s);
return s;
} catch (NumberFormatException e) {
// ignored
}
return "'" + s + "'";
} }
public DataType getDataType() { public DataType getDataType() {

View File

@ -0,0 +1,69 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class PreparedStatementBuilder {
private final StringBuilder sb = new StringBuilder();
private final List<String> variables = new ArrayList<>();
public PreparedStatementBuilder() {
}
public PreparedStatementBuilder append(String s) {
this.sb.append(s);
return this;
}
public PreparedStatementBuilder variable(String variable) {
this.variables.add(variable);
return this;
}
public PreparedStatement build(Connection connection, Function<String, String> mapping) throws SQLException {
PreparedStatement statement = connection.prepareStatement(mapping.apply(this.sb.toString()));
for (int i = 0; i < this.variables.size(); i++) {
String var = this.variables.get(i);
statement.setString(i + 1, var);
}
return statement;
}
public String toReadableString() {
String s = this.sb.toString();
for (String var : this.variables) {
s = s.replaceFirst("\\?", var);
}
return s;
}
}

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.bulkupdate.action; package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.node.model.NodeDataContainer; import me.lucko.luckperms.common.node.model.NodeDataContainer;
/** /**
@ -54,6 +55,6 @@ public interface Action {
* *
* @return the action in sql form * @return the action in sql form
*/ */
String getAsSql(); void appendSql(PreparedStatementBuilder builder);
} }

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.bulkupdate.action; package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.node.model.NodeDataContainer; import me.lucko.luckperms.common.node.model.NodeDataContainer;
public class DeleteAction implements Action { public class DeleteAction implements Action {
@ -47,7 +48,7 @@ public class DeleteAction implements Action {
} }
@Override @Override
public String getAsSql() { public void appendSql(PreparedStatementBuilder builder) {
return "DELETE FROM {table}"; builder.append("DELETE FROM {table}");
} }
} }

View File

@ -25,7 +25,7 @@
package me.lucko.luckperms.common.bulkupdate.action; package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.query.QueryField; import me.lucko.luckperms.common.bulkupdate.query.QueryField;
import me.lucko.luckperms.common.node.model.NodeDataContainer; import me.lucko.luckperms.common.node.model.NodeDataContainer;
@ -66,7 +66,8 @@ public class UpdateAction implements Action {
} }
@Override @Override
public String getAsSql() { public void appendSql(PreparedStatementBuilder builder) {
return "UPDATE {table} SET " + this.field.getSqlName() + "=" + BulkUpdate.escapeStringForSql(this.value); builder.append("UPDATE {table} SET " + this.field.getSqlName() + "=?");
builder.variable(this.value);
} }
} }

View File

@ -25,6 +25,8 @@
package me.lucko.luckperms.common.bulkupdate.comparisons; package me.lucko.luckperms.common.bulkupdate.comparisons;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
/** /**
* A method of comparing two strings * A method of comparing two strings
*/ */
@ -49,9 +51,7 @@ public interface Comparison {
/** /**
* Returns the comparison operator in SQL form * Returns the comparison operator in SQL form
*
* @return a sql form of this comparison
*/ */
String getAsSql(); void appendSql(PreparedStatementBuilder builder);
} }

View File

@ -25,7 +25,7 @@
package me.lucko.luckperms.common.bulkupdate.comparisons; package me.lucko.luckperms.common.bulkupdate.comparisons;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
public class Constraint { public class Constraint {
@ -54,8 +54,12 @@ public class Constraint {
return this.comparison.matches(value, this.expression); return this.comparison.matches(value, this.expression);
} }
public String getAsSql(String field) { public void appendSql(PreparedStatementBuilder builder, String field) {
return field + " " + this.comparison.getAsSql() + " " + BulkUpdate.escapeStringForSql(this.expression); // e.g. field LIKE ?
builder.append(field + " ");
this.comparison.appendSql(builder);
builder.append(" ?");
builder.variable(this.expression);
} }
public Comparison getComparison() { public Comparison getComparison() {

View File

@ -25,6 +25,8 @@
package me.lucko.luckperms.common.bulkupdate.comparisons; package me.lucko.luckperms.common.bulkupdate.comparisons;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
/** /**
* An enumeration of standard {@link Comparison}s. * An enumeration of standard {@link Comparison}s.
*/ */
@ -88,8 +90,8 @@ public enum StandardComparison implements Comparison {
} }
@Override @Override
public String getAsSql() { public void appendSql(PreparedStatementBuilder builder) {
return this.asSql; builder.append(this.asSql);
} }
public static StandardComparison parseComparison(String s) { public static StandardComparison parseComparison(String s) {

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.bulkupdate.query; package me.lucko.luckperms.common.bulkupdate.query;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.comparisons.Constraint; import me.lucko.luckperms.common.bulkupdate.comparisons.Constraint;
import me.lucko.luckperms.common.node.model.NodeDataContainer; import me.lucko.luckperms.common.node.model.NodeDataContainer;
@ -67,8 +68,8 @@ public class Query {
} }
} }
public String getAsSql() { public void appendSql(PreparedStatementBuilder builder) {
return this.constraint.getAsSql(this.field.getSqlName()); this.constraint.appendSql(builder, this.field.getSqlName());
} }
public QueryField getField() { public QueryField getField() {

View File

@ -151,7 +151,7 @@ public class BulkUpdateCommand extends SingleCommand {
this.pendingOperations.put(id, bulkUpdate); this.pendingOperations.put(id, bulkUpdate);
Message.BULK_UPDATE_QUEUED.send(sender, bulkUpdate.buildAsSql().replace("{table}", bulkUpdate.getDataType().getName())); Message.BULK_UPDATE_QUEUED.send(sender, bulkUpdate.buildAsSql().toReadableString().replace("{table}", bulkUpdate.getDataType().getName()));
Message.BULK_UPDATE_CONFIRM.send(sender, label, id); Message.BULK_UPDATE_CONFIRM.send(sender, label, id);
return CommandResult.SUCCESS; return CommandResult.SUCCESS;

View File

@ -35,6 +35,7 @@ import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.actionlog.Log; import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.comparisons.Constraint; import me.lucko.luckperms.common.bulkupdate.comparisons.Constraint;
import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer; import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import me.lucko.luckperms.common.managers.group.GroupManager; import me.lucko.luckperms.common.managers.group.GroupManager;
@ -265,20 +266,18 @@ public class SqlDao extends AbstractDao {
@Override @Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) throws SQLException { public void applyBulkUpdate(BulkUpdate bulkUpdate) throws SQLException {
String queryString = bulkUpdate.buildAsSql();
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
if (bulkUpdate.getDataType().isIncludingUsers()) { if (bulkUpdate.getDataType().isIncludingUsers()) {
String table = this.prefix.apply("{prefix}user_permissions"); String table = this.prefix.apply("{prefix}user_permissions");
try (Statement s = c.createStatement()) { try (PreparedStatement ps = bulkUpdate.buildAsSql().build(c, q -> q.replace("{table}", table))) {
s.execute(queryString.replace("{table}", table)); ps.execute();
} }
} }
if (bulkUpdate.getDataType().isIncludingGroups()) { if (bulkUpdate.getDataType().isIncludingGroups()) {
String table = this.prefix.apply("{prefix}group_permissions"); String table = this.prefix.apply("{prefix}group_permissions");
try (Statement s = c.createStatement()) { try (PreparedStatement ps = bulkUpdate.buildAsSql().build(c, q -> q.replace("{table}", table))) {
s.execute(queryString.replace("{table}", table)); ps.execute();
} }
} }
} }
@ -494,9 +493,12 @@ public class SqlDao extends AbstractDao {
@Override @Override
public List<HeldPermission<UUID>> getUsersWithPermission(Constraint constraint) throws SQLException { public List<HeldPermission<UUID>> getUsersWithPermission(Constraint constraint) throws SQLException {
PreparedStatementBuilder builder = new PreparedStatementBuilder().append(USER_PERMISSIONS_SELECT_PERMISSION);
constraint.appendSql(builder, "permission");
List<HeldPermission<UUID>> held = new ArrayList<>(); List<HeldPermission<UUID>> held = new ArrayList<>();
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(USER_PERMISSIONS_SELECT_PERMISSION + constraint.getAsSql("permission")))) { try (PreparedStatement ps = builder.build(c, this.prefix)) {
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) { while (rs.next()) {
UUID holder = UUID.fromString(rs.getString("uuid")); UUID holder = UUID.fromString(rs.getString("uuid"));
@ -737,9 +739,12 @@ public class SqlDao extends AbstractDao {
@Override @Override
public List<HeldPermission<String>> getGroupsWithPermission(Constraint constraint) throws SQLException { public List<HeldPermission<String>> getGroupsWithPermission(Constraint constraint) throws SQLException {
PreparedStatementBuilder builder = new PreparedStatementBuilder().append(GROUP_PERMISSIONS_SELECT_PERMISSION);
constraint.appendSql(builder, "permission");
List<HeldPermission<String>> held = new ArrayList<>(); List<HeldPermission<String>> held = new ArrayList<>();
try (Connection c = this.provider.getConnection()) { try (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(GROUP_PERMISSIONS_SELECT_PERMISSION + constraint.getAsSql("permission")))) { try (PreparedStatement ps = builder.build(c, this.prefix)) {
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) { while (rs.next()) {
String holder = rs.getString("name"); String holder = rs.getString("name");