started adding comments, restructured Database class
This commit is contained in:
@@ -12,72 +12,130 @@ import java.util.*;
|
||||
|
||||
import static de.srsoftware.widerhall.Util.t;
|
||||
|
||||
/**
|
||||
* @author Stephan Richter, 2022
|
||||
* This class abstracts away all needed database operations
|
||||
*/
|
||||
public class Database {
|
||||
public static final String HASHED_PASS = "hashedPassword";
|
||||
public static final String SALT = "salt";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Database.class);
|
||||
private static Database singleton = null;
|
||||
private final Connection conn;
|
||||
private static Database singleton = null; // we only need one db handle ever. This will be it.
|
||||
private final Connection conn; // the actual db connection handle within the singleton
|
||||
|
||||
public class Request{
|
||||
public class CompiledRequest{
|
||||
private final String sql;
|
||||
private final List<Object> args;
|
||||
|
||||
private final StringBuilder sql;
|
||||
private final HashMap<String, List<Object>> where = new HashMap<>();
|
||||
private final HashMap<String,Object> values = new HashMap<>();
|
||||
private final HashMap<String,Object> setValues = new HashMap<>();
|
||||
|
||||
public Request(String sql) {
|
||||
this(new StringBuilder(sql));
|
||||
public CompiledRequest(String sql, List<Object> args){
|
||||
this.sql = sql;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* build the final SQL and execute the actual request (for SELECT statements)
|
||||
* @return the result set produced by the SELECT statement
|
||||
* @throws SQLException
|
||||
*/
|
||||
public ResultSet exec() throws SQLException {
|
||||
LOG.debug("Executing {}",this);
|
||||
try {
|
||||
var stmt = conn.prepareStatement(sql);
|
||||
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i));
|
||||
return stmt.executeQuery();
|
||||
} catch (SQLException sqle) { // add sql to the exception
|
||||
throw new SQLException(t("Query '{}' failed:",this),sqle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* build the final SQL and execure the actual request (for statements that generate no result)
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void run() throws SQLException {
|
||||
LOG.debug("Running {}",this);
|
||||
try {
|
||||
var stmt = conn.prepareStatement(sql);
|
||||
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i));
|
||||
stmt.execute();
|
||||
} catch (SQLException sqle) {
|
||||
throw new SQLException(t("Query '{}' failed:",this),sqle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var filled = sql;
|
||||
int pos = filled.indexOf('?');
|
||||
int idx = 0;
|
||||
while (pos>0){
|
||||
var fill = args.get(idx++);
|
||||
var s = fill == null ? "NULL" : (fill instanceof Number ? ""+fill : "'"+fill+"'");
|
||||
filled = filled.substring(0,pos)+s+filled.substring(pos+1);
|
||||
pos = filled.indexOf('?',pos+s.length());
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper class to allow daisy-chaining request modifiers
|
||||
*/
|
||||
public class Request{
|
||||
|
||||
private final StringBuilder sql; // buffer the sql to be built
|
||||
private final HashMap<String, List<Object>> where = new HashMap<>(); // buffer condition statements for select
|
||||
private final HashMap<String,Object> values = new HashMap<>(); // buffer values for insert/update statements
|
||||
|
||||
/**
|
||||
* Start to create a new request with the initial SQL
|
||||
* @param sql initial sql for the request
|
||||
*/
|
||||
public Request(StringBuilder sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
public ResultSet exec() throws SQLException {
|
||||
LOG.debug("Executing {}",this);
|
||||
var args = new ArrayList<>();
|
||||
/**
|
||||
* conditions are assembled in the following form:
|
||||
* SELECT … FROM … WHERE <key> in (<values>)
|
||||
* <values> are build as list of question marks, whose arguments are applied later
|
||||
* @param args
|
||||
*/
|
||||
private void applyConditions(ArrayList<Object> args){
|
||||
if (!where.isEmpty()){
|
||||
var clauses = new ArrayList<String>();
|
||||
sql.append(" WHERE ");
|
||||
|
||||
for (var entry : where.entrySet()){
|
||||
var arr = new String[entry.getValue().size()];
|
||||
Arrays.fill(arr,"?");
|
||||
var marks = String.join(", ",arr);
|
||||
clauses.add("("+entry.getKey()+" IN ("+marks+"))");
|
||||
args.addAll(entry.getValue());
|
||||
{ // build the list of question marks
|
||||
var arr = new String[entry.getValue().size()];
|
||||
Arrays.fill(arr, "?");
|
||||
var marks = String.join(", ", arr);
|
||||
clauses.add("(" + entry.getKey() + " IN (" + marks + "))");
|
||||
}
|
||||
args.addAll(entry.getValue()); // will be applied to the prepared statement later
|
||||
}
|
||||
sql.append(String.join(" AND ",clauses));
|
||||
|
||||
}
|
||||
try {
|
||||
var stmt = Database.this.conn.prepareStatement(sql());
|
||||
if (!args.isEmpty()) {
|
||||
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i));
|
||||
}
|
||||
return stmt.executeQuery();
|
||||
} catch (SQLException sqle) {
|
||||
throw new SQLException(t("Query '{}' failed:",sql),sqle);
|
||||
}
|
||||
}
|
||||
|
||||
public void run() throws SQLException {
|
||||
LOG.debug("Running {}",this);
|
||||
@Override
|
||||
protected Request clone() {
|
||||
Request clone = new Request(sql);
|
||||
clone.where.putAll(where);
|
||||
clone.values.putAll(values);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public CompiledRequest compile(){
|
||||
var args = new ArrayList<>();
|
||||
applyValues(args);
|
||||
applyConditions(args);
|
||||
return new CompiledRequest(sql.toString(),args);
|
||||
}
|
||||
|
||||
if (!setValues.isEmpty()){
|
||||
var keys = new ArrayList<String>();
|
||||
var expressions = new ArrayList<String>();
|
||||
for (var entry : setValues.entrySet()) {
|
||||
expressions.add(entry.getKey()+" = ?");
|
||||
args.add(entry.getValue());
|
||||
}
|
||||
sql.append(" SET ").append(String.join(", ",expressions));
|
||||
}
|
||||
|
||||
if (!values.isEmpty()){
|
||||
private void applyValues(ArrayList<Object> args){
|
||||
if (values.isEmpty()) return;
|
||||
var currentSql = sql();
|
||||
if (currentSql.startsWith("INSERT")){
|
||||
var keys = new ArrayList<String>();
|
||||
for (var entry : values.entrySet()) {
|
||||
keys.add(entry.getKey());
|
||||
@@ -92,34 +150,18 @@ public class Database {
|
||||
var marks = String.join(", ",arr);
|
||||
sql.append("(").append(marks).append(")");
|
||||
}
|
||||
|
||||
if (!where.isEmpty()){
|
||||
var clauses = new ArrayList<String>();
|
||||
sql.append(" WHERE ");
|
||||
|
||||
for (var entry : where.entrySet()){
|
||||
var arr = new String[entry.getValue().size()];
|
||||
Arrays.fill(arr,"?");
|
||||
var marks = String.join(", ",arr);
|
||||
clauses.add("("+entry.getKey()+" IN ("+marks+"))");
|
||||
args.addAll(entry.getValue());
|
||||
if (currentSql.startsWith("UPDATE")){
|
||||
var expressions = new ArrayList<String>();
|
||||
for (var entry : values.entrySet()) {
|
||||
expressions.add(entry.getKey()+" = ?");
|
||||
args.add(entry.getValue());
|
||||
}
|
||||
sql.append(String.join(" AND ",clauses));
|
||||
|
||||
}
|
||||
try {
|
||||
var stmt = conn.prepareStatement(sql());
|
||||
if (!args.isEmpty()) {
|
||||
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i));
|
||||
}
|
||||
stmt.execute();
|
||||
} catch (SQLException sqle) {
|
||||
throw new SQLException(t("Query '{}' failed:",sql),sqle);
|
||||
sql.append(" SET ").append(String.join(", ",expressions));
|
||||
}
|
||||
}
|
||||
|
||||
public Request set(String key, Object value) {
|
||||
setValues.put(key,value);
|
||||
values.put(key,value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -129,40 +171,7 @@ public class Database {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sql = new StringBuffer(this.sql);
|
||||
|
||||
if (!setValues.isEmpty()){
|
||||
var keys = new ArrayList<String>();
|
||||
var expressions = new ArrayList<String>();
|
||||
for (var entry : setValues.entrySet()) expressions.add(entry.getKey()+" = "+entry.getValue());
|
||||
sql.append(" SET ").append(String.join(", ",expressions));
|
||||
}
|
||||
|
||||
if (!values.isEmpty()){
|
||||
var keys = new ArrayList<String>();
|
||||
var vals = new ArrayList<String>();
|
||||
for (var entry : values.entrySet()) {
|
||||
keys.add(entry.getKey());
|
||||
vals.add(entry.getValue().toString());
|
||||
}
|
||||
sql.append("(")
|
||||
.append(String.join(", ",keys))
|
||||
.append(")")
|
||||
.append(" VALUES ")
|
||||
.append("(")
|
||||
.append(String.join(",",vals))
|
||||
.append(")");
|
||||
}
|
||||
|
||||
if (!where.isEmpty()){
|
||||
var clauses = new ArrayList<String>();
|
||||
sql.append(" WHERE ");
|
||||
|
||||
for (var entry : where.entrySet()) clauses.add("("+entry.getKey()+" IN ("+String.join(", ",entry.getValue().stream().map(Object::toString).toList())+"))");
|
||||
sql.append(String.join(" AND ",clauses));
|
||||
}
|
||||
|
||||
return "Request{" + "sql=" + sql + '}';
|
||||
return "Request("+clone().compile()+')';
|
||||
}
|
||||
|
||||
public Request values(Map<String,Object> newValues) {
|
||||
@@ -170,11 +179,6 @@ public class Database {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request values(String key, Object value) {
|
||||
values.put(key,value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request where(String key, Object ... values) {
|
||||
for (var val : values) where(key,val);
|
||||
return this;
|
||||
@@ -254,7 +258,7 @@ public class Database {
|
||||
var sql = new StringBuilder("SELECT EXISTS (SELECT name FROM sqlite_schema WHERE type='table' AND name='")
|
||||
.append(tbName)
|
||||
.append("')");
|
||||
ResultSet rs = query( sql).exec();
|
||||
ResultSet rs = query(sql).compile().exec();
|
||||
int val = 0;
|
||||
if (rs.next()) val = rs.getInt(1);
|
||||
rs.close();
|
||||
|
||||
Reference in New Issue
Block a user