diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index f0de53c..32036ab 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -24,7 +24,6 @@ import de.steamwar.sql.internal.SelectStatement; import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; -import lombok.Getter; import java.util.HashSet; import java.util.Set; @@ -32,6 +31,10 @@ import java.util.Set; @AllArgsConstructor public class NodeMember { + public static void init() { + // enforce class initialization + } + private static final Table table = new Table<>(NodeMember.class); private static final SelectStatement getNodeMember = table.select(Table.PRIMARY); private static final SelectStatement getNodeMembers = table.selectFields("Node"); @@ -39,15 +42,21 @@ public class NodeMember { private static final Statement create = table.insertAll(); private static final Statement delete = table.delete(Table.PRIMARY); - @Getter @Field(keys = {Table.PRIMARY}) - private final int node; - @Getter + private final int nodeId; @Field(keys = {Table.PRIMARY}) - private final int member; + private final int userId; + + public int getNodeId() { + return nodeId; + } + + public int getUserId() { + return userId; + } public void delete() { - delete.update(node, member); + delete.update(nodeId, userId); } public static NodeMember createNodeMember(int node, int member) { diff --git a/src/de/steamwar/sql/Replay.java b/src/de/steamwar/sql/Replay.java index 90d2b34..a90e239 100644 --- a/src/de/steamwar/sql/Replay.java +++ b/src/de/steamwar/sql/Replay.java @@ -28,6 +28,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.sql.SQLException; @AllArgsConstructor @@ -36,9 +37,9 @@ public class Replay { static { new SqlTypeMapper<>(File.class, "BLOB", (rs, identifier) -> { try { - File file = File.createTempFile("replay", "replay"); + File file = File.createTempFile("replay", ".replay"); file.deleteOnExit(); - Files.copy(rs.getBinaryStream(identifier), file.toPath()); + Files.copy(rs.getBinaryStream(identifier), file.toPath(), StandardCopyOption.REPLACE_EXISTING); return file; } catch (IOException e) { throw new SQLException(e); diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index 4354867..0a8ece3 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -40,7 +40,7 @@ public class SWException { private static final Table table = new Table<>(SWException.class, "Exception"); private static final Statement insert = table.insertFields("server", "message", "stacktrace"); - @Field(keys = {Table.PRIMARY}) + @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int id; @Field(def = "CURRENT_TIMESTAMP") private final Timestamp time; diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index cc62acc..be7f1b1 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -72,6 +72,10 @@ public class SchematicNode { private static final SelectStatement allAccessibleByUser = new SelectStatement<>(table, "WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN ORDER BY NodeName"); private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (" + nodeSelectCreator("") + "WHERE NodeId = ? UNION " + nodeSelectCreator("SN.") + "SN, RSN WHERE RSN.ParentNode = SN.NodeId) SELECT * FROM RSN ORDER BY NodeName"); + static { + NodeMember.init(); + } + @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int nodeId; @Field(keys = {"OwnerNameParent"}) @@ -476,7 +480,7 @@ public class SchematicNode { final Set nodeMembers = NodeMember.getSchematics(user.getId()); AtomicInteger i = new AtomicInteger(); i.set(currentNode.getId()); - while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) { + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNodeId() == i.get())) { currentNode = currentNode.getParentNode(); i.set(currentNode.getId()); builder.insert(0, split) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index c5ccbd5..874643f 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -101,7 +101,7 @@ public class SteamwarUser { this.leader = leader; this.locale = locale; this.manualLocale = manualLocale; - this.discordId = discordId != 0 ? discordId : null; + this.discordId = discordId != null && discordId != 0 ? discordId : null; usersById.put(id, this); usersByName.put(userName.toLowerCase(), this); @@ -118,7 +118,16 @@ public class SteamwarUser { public Locale getLocale() { if(locale != null) return locale; - return Locale.getDefault(); //TODO test correct locale on join (z.B. FightSystem Hotbarkit) + return Locale.getDefault(); + } + + public void setLocale(Locale locale, boolean manualLocale) { + if (locale == null || (this.manualLocale && !manualLocale)) + return; + + this.locale = locale; + this.manualLocale = manualLocale; + updateLocale.update(locale.toLanguageTag(), manualLocale, id); } public static SteamwarUser get(String userName){ diff --git a/src/de/steamwar/sql/internal/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java index 0f4a329..e17dcb7 100644 --- a/src/de/steamwar/sql/internal/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -31,7 +31,7 @@ public class SelectStatement extends Statement { private final Table table; SelectStatement(Table table, String... kfields) { - this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); } public SelectStatement(Table table, String sql) { diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index 5c41010..ae3b7c8 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -47,7 +47,7 @@ public final class SqlTypeMapper { public static > void nameEnumMapper(Class type) { new SqlTypeMapper<>( type, - "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).get() + ")", + "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).orElse(0) + ")", (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)), (st, index, value) -> st.setString(index, value.name()) ); @@ -57,7 +57,7 @@ public final class SqlTypeMapper { primitiveMapper(boolean.class, Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); primitiveMapper(byte.class, Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); primitiveMapper(short.class, Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); - primitiveMapper(int.class, Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); + primitiveMapper(int.class, Integer.class, "INTEGER", ResultSet::getInt, PreparedStatement::setInt); primitiveMapper(long.class, Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); primitiveMapper(float.class, Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); primitiveMapper(double.class, Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 152784f..88aa5d6 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -26,6 +26,7 @@ import java.sql.*; import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,15 +39,17 @@ public class Statement implements AutoCloseable { private static final int MAX_CONNECTIONS; private static final Supplier conProvider; static final Consumer> schemaCreator; + static final String ON_DUPLICATE_KEY; + static final UnaryOperator upsertWrapper; - private static final boolean mysqlMode; - private static final boolean productionDatabase; + private static final boolean MYSQL_MODE; + private static final boolean PRODUCTION_DATABASE; static { File file = new File(System.getProperty("user.home"), "mysql.properties"); - mysqlMode = file.exists(); + MYSQL_MODE = file.exists(); - if(mysqlMode) { + if(MYSQL_MODE) { Properties properties = new Properties(); try { properties.load(new FileReader(file)); @@ -58,7 +61,7 @@ public class Statement implements AutoCloseable { String user = properties.getProperty("user"); String password = properties.getProperty("password"); - productionDatabase = "core".equals(properties.getProperty("database")); + PRODUCTION_DATABASE = "core".equals(properties.getProperty("database")); MAX_CONNECTIONS = SQLConfig.impl.maxConnections(); conProvider = () -> { try { @@ -68,6 +71,8 @@ public class Statement implements AutoCloseable { } }; schemaCreator = table -> {}; + ON_DUPLICATE_KEY = " ON DUPLICATE KEY UPDATE "; + upsertWrapper = f -> f + " = VALUES(" + f + ")"; } else { Connection connection; @@ -78,10 +83,12 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create sqlite connection", e); } - productionDatabase = false; + PRODUCTION_DATABASE = false; MAX_CONNECTIONS = 1; conProvider = () -> connection; schemaCreator = Table::ensureExistanceInSqlite; + ON_DUPLICATE_KEY = " ON CONFLICT DO UPDATE SET "; + upsertWrapper = f -> f + " = " + f; } } @@ -99,11 +106,11 @@ public class Statement implements AutoCloseable { } public static boolean mysqlMode() { - return mysqlMode; + return MYSQL_MODE; } public static boolean productionDatabase() { - return productionDatabase; + return PRODUCTION_DATABASE; } private final boolean returnGeneratedKeys; diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index f9dd6f7..fb72709 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -53,7 +53,7 @@ public class Table { keys = Arrays.stream(fields).flatMap(field -> Arrays.stream(field.field.keys())).distinct().collect(Collectors.toMap(Function.identity(), key -> Arrays.stream(fields).filter(field -> Arrays.asList(field.field.keys()).contains(key)).toArray(TableField[]::new))); for (TableField field : fields) { - fieldsByIdentifier.put(field.identifier, field); + fieldsByIdentifier.put(field.identifier.toLowerCase(), field); } Statement.schemaCreator.accept(this); @@ -91,8 +91,8 @@ public class Table { } public Statement insertFields(boolean returnGeneratedKeys, String... fields) { - List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f).field.keys().length == 0).collect(Collectors.toList()); - return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", "))), returnGeneratedKeys); + List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f.toLowerCase()).field.keys().length == 0).collect(Collectors.toList()); + return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : Statement.ON_DUPLICATE_KEY + nonKeyFields.stream().map(Statement.upsertWrapper).collect(Collectors.joining(", "))), returnGeneratedKeys); } public Statement delete(String name) { @@ -104,12 +104,11 @@ public class Table { } void ensureExistanceInSqlite() { - List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); try (Statement statement = new Statement( "CREATE TABLE IF NOT EXISTS " + name + "(" + - Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + - keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + - ") WITHOUT ROWID")) { + Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def())).collect(Collectors.joining(", ")) + + keys.entrySet().stream().map(key -> (key.getKey().equals(PRIMARY) ? ", PRIMARY KEY(" : ", UNIQUE (") + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + + ")")) { statement.update(); } }