|
|
|
@ -12,72 +12,130 @@ import java.util.*;
@@ -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; |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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<>(); |
|
|
|
|
/** |
|
|
|
|
* 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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Request(String sql) { |
|
|
|
|
this(new StringBuilder(sql)); |
|
|
|
|
@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); |
|
|
|
|
var args = new ArrayList<>(); |
|
|
|
|
@Override |
|
|
|
|
protected Request clone() { |
|
|
|
|
Request clone = new Request(sql); |
|
|
|
|
clone.where.putAll(where); |
|
|
|
|
clone.values.putAll(values); |
|
|
|
|
return clone; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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)); |
|
|
|
|
} |
|
|
|
|
public CompiledRequest compile(){ |
|
|
|
|
var args = new ArrayList<>(); |
|
|
|
|
applyValues(args); |
|
|
|
|
applyConditions(args); |
|
|
|
|
return new CompiledRequest(sql.toString(),args); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 {
@@ -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()); |
|
|
|
|
} |
|
|
|
|
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)); |
|
|
|
|
if (currentSql.startsWith("UPDATE")){ |
|
|
|
|
var expressions = new ArrayList<String>(); |
|
|
|
|
for (var entry : values.entrySet()) { |
|
|
|
|
expressions.add(entry.getKey()+" = ?"); |
|
|
|
|
args.add(entry.getValue()); |
|
|
|
|
} |
|
|
|
|
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 {
@@ -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 {
@@ -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 {
@@ -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(); |
|
|
|
|