diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 8424665..2b40c8f 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -19,21 +19,58 @@ package de.steamwar.sql; -public class Field { +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class Field { + + private static final Map, String> sqlTypeMapping = new HashMap<>(); + private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); + + public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { + sqlTypeMapping.put(clazz, sqlType); + sqlTypeParser.put(clazz, parser); + } + + static { + addTypeMapping(String.class, "TEXT", (rs, field) -> rs.getString(field.identifier())); + addTypeMapping(boolean.class, "INTEGER(1)", (rs, field) -> rs.getBoolean(field.identifier())); + addTypeMapping(byte.class, "INTEGER(1)", (rs, field) -> rs.getByte(field.identifier())); + addTypeMapping(short.class, "INTEGER(2)", (rs, field) -> rs.getShort(field.identifier())); + addTypeMapping(int.class, "INTEGER(4)", (rs, field) -> rs.getInt(field.identifier())); + addTypeMapping(long.class, "INTEGER(8)", (rs, field) -> rs.getLong(field.identifier())); + addTypeMapping(float.class, "REAL", (rs, field) -> rs.getFloat(field.identifier())); + addTypeMapping(double.class, "REAL", (rs, field) -> rs.getDouble(field.identifier())); + } private final String identifier; - private final Class type; + private final Class type; - public Field(String identifier, Class type) { + private final SqlTypeParser parser; + private final String sqlType; + + public Field(String identifier, Class type) { this.identifier = identifier; this.type = type; + this.parser = (SqlTypeParser) sqlTypeParser.get(type); + this.sqlType = sqlTypeMapping.get(type); } public String identifier() { return identifier; } - public Class type() { + public Class type() { return type; } + + public String sqlType() { + return sqlType; + } + + public T parse(ResultSet rs) throws SQLException { + return parser.parse(rs, this); + } } diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java index c9ae439..afd7056 100644 --- a/src/de/steamwar/sql/Row.java +++ b/src/de/steamwar/sql/Row.java @@ -24,12 +24,16 @@ public class Row { private final Table table; private Object[] values; - public Row(Table table, Object[] values) { + public Row(Table table, Object... values) { this.table = table; this.values = values; } - void update(Field field, Object value) { + private T get(Field field) { + return (T) values[table.getFieldId(field)]; + } + void update(Field[] fields, Object... values) { + table.update(values[table.keyId()], fields, values); } } diff --git a/src/de/steamwar/sql/SqlTypeParser.java b/src/de/steamwar/sql/SqlTypeParser.java new file mode 100644 index 0000000..377097c --- /dev/null +++ b/src/de/steamwar/sql/SqlTypeParser.java @@ -0,0 +1,27 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface SqlTypeParser { + T parse(ResultSet rs, Field field) throws SQLException; +} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index a54a4ee..bc9e32f 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -47,7 +47,7 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not load SQL connection", e); } - String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?autoReconnect=true&useServerPrepStmts=true"; + String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?useServerPrepStmts=true"; String user = properties.getProperty("user"); String password = properties.getProperty("password"); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index 239b3f8..4027532 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -19,69 +19,96 @@ package de.steamwar.sql; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.*; -import java.util.logging.Level; import java.util.stream.Collectors; public class Table { private final String name; - private final Field key; - private final Field[] fields; + private final Field key; + private final Field[] fields; + private final Map, Integer> fieldIds = new HashMap<>(); + private final int keyId; - private final Map selectBy = new HashMap<>(); + private final Map, Statement> cachedSelect = new HashMap<>(); + private final Map[], Statement> cachedInsert = new HashMap<>(); + private final Map[], Statement> cachedUpdate = new HashMap<>(); - public Table(String name, Field key, Field... fields) { + public Table(String name, Field key, Field... fields) { this.name = name; this.key = key; this.fields = fields; + for(int i = 0; i < fields.length; i++) { + fieldIds.put(fields[i], i); + } + keyId = fieldIds.get(key); } - public Row selectSingle(Field field, Object value) { + public Row selectSingle(Field field, Object value) { return select(rs -> { - if(!rs.next()) - return null; - - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].type().cast(rs.getObject(i)); - } - if(rs.next()) - SQLConfig.impl.getLogger().log(Level.WARNING, "Statement returned more than one result " + selectBy.get(field).getSql() + " for value " + value); - - return new Row(this, values); + return read(rs); + return null; }, field, value); } - public List selectMulti(Field field, Object value) { + public List selectMulti(Field field, Object value) { return select(rs -> { List result = new ArrayList<>(); - while(rs.next()) { - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].type().cast(rs.getObject(i)); - } + while(rs.next()) + result.add(read(rs)); - result.add(new Row(this, values)); - } return result; }, field, value); } - public void create() { - //TODO and alter table - } - - public Field getKey() { - return key; - } - - private T select(Statement.ResultSetUser u, Field field, Object value) { + public void insert(Field[] fields, Object... values) { Statement statement; - synchronized (selectBy) { - statement = selectBy.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + synchronized (cachedInsert) { + statement = cachedInsert.computeIfAbsent(fields, fs -> new Statement("INSERT INTO " + name + " (" + Arrays.stream(fs).map(Field::identifier).collect(Collectors.joining(", ")) + ") VALUES (" + Arrays.stream(fs).map(f -> "?").collect(Collectors.joining(", ")) + ")")); + } + statement.update(values); + } + + public void update(Object keyvalue, Field[] fields, Object... values) { + Statement statement; + synchronized (cachedUpdate) { + statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + key.identifier() + " = ?")); + } + statement.update(values, keyvalue); + } + + public void create() { + //TODO syntax mysql/sqlite + try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (field == key ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + statement.update(); + } + } + + public int keyId() { + return keyId; + } + + public int getFieldId(Field field) { + return fieldIds.get(field); + } + + private T select(Statement.ResultSetUser u, Field field, Object value) { + Statement statement; + synchronized (cachedSelect) { + statement = cachedSelect.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); } return statement.select(u, value); } + + private Row read(ResultSet rs) throws SQLException { + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].parse(rs); + } + + return new Row(this, values); + } }