|
|
|
@ -1,31 +1,30 @@
@@ -1,31 +1,30 @@
|
|
|
|
|
/* © SRSoftware 2024 */ |
|
|
|
|
package de.srsoftware.cal.db; |
|
|
|
|
|
|
|
|
|
import static de.srsoftware.tools.Optionals.allEmpty; |
|
|
|
|
import static de.srsoftware.tools.Optionals.nullable; |
|
|
|
|
import static de.srsoftware.tools.Optionals.*; |
|
|
|
|
import static de.srsoftware.tools.Strings.camelCase; |
|
|
|
|
import static de.srsoftware.tools.jdbc.Condition.equal; |
|
|
|
|
import static de.srsoftware.tools.jdbc.Condition.moreThan; |
|
|
|
|
import static de.srsoftware.tools.jdbc.Query.MARK; |
|
|
|
|
import static java.lang.System.Logger.Level.*; |
|
|
|
|
|
|
|
|
|
import de.srsoftware.cal.BaseAppointment; |
|
|
|
|
import de.srsoftware.cal.api.Appointment; |
|
|
|
|
import de.srsoftware.tools.Calc; |
|
|
|
|
import de.srsoftware.tools.Error; |
|
|
|
|
import de.srsoftware.tools.Payload; |
|
|
|
|
import de.srsoftware.tools.Result; |
|
|
|
|
import de.srsoftware.tools.jdbc.Query; |
|
|
|
|
import java.sql.Connection; |
|
|
|
|
import java.sql.DriverManager; |
|
|
|
|
import java.sql.SQLException; |
|
|
|
|
import java.sql.Timestamp; |
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
import java.util.HashMap; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Set; |
|
|
|
|
import java.sql.*; |
|
|
|
|
import java.time.LocalDateTime; |
|
|
|
|
import java.util.*; |
|
|
|
|
|
|
|
|
|
public class MariaDB implements Database { |
|
|
|
|
private static final String SELECT_APPOINTMENTS = "SELECT * FROM appointments"; |
|
|
|
|
private static final String SELECT_VERSION = "SELECT value FROM config WHERE keyname = 'dbversion'"; |
|
|
|
|
private static final String ADD_SLUG = "ALTER TABLE appointments ADD slug VARCHAR(255) UNIQUE"; |
|
|
|
|
private static final String INSERT_HASH = "INSERT INTO appointment_hashes (aid, hash) values (?, ?) ON DUPLICATE KEY UPDATE hash=hash;"; |
|
|
|
|
private static final String UPDATE_DB_VERSION = "UPDATE config SET value = ? WHERE keyname = 'dbversion'"; |
|
|
|
|
private static final String INSERT_SLUG = "UPDATE IGNORE appointments SET slug = ? WHERE aid = ?"; |
|
|
|
|
private static Connection connection; |
|
|
|
|
private static final System.Logger LOG = System.getLogger(MariaDB.class.getSimpleName()); |
|
|
|
|
private static final String ADD_SLUG = "ALTER TABLE appointments ADD slug VARCHAR(255) UNIQUE"; |
|
|
|
|
private static final String APPOINTMENTS = "appointments"; |
|
|
|
|
private static final String AID = "aid"; |
|
|
|
|
private static Connection connection; |
|
|
|
|
|
|
|
|
|
private MariaDB(Connection conn) throws SQLException { |
|
|
|
|
connection = conn; |
|
|
|
@ -33,7 +32,8 @@ public class MariaDB implements Database {
@@ -33,7 +32,8 @@ public class MariaDB implements Database {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void applyUpdates() throws SQLException { |
|
|
|
|
var rs = Query.of(SELECT_VERSION).execute(connection); |
|
|
|
|
LOG.log(INFO, "Checking for updates…"); |
|
|
|
|
var rs = Query.select("value").from("config").where("keyname", equal("dbversion")).exec(connection); |
|
|
|
|
var version = 0; |
|
|
|
|
if (rs.next()) { |
|
|
|
|
version = rs.getInt("value"); |
|
|
|
@ -48,35 +48,39 @@ public class MariaDB implements Database {
@@ -48,35 +48,39 @@ public class MariaDB implements Database {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void update1() throws SQLException { |
|
|
|
|
LOG.log(INFO, "Updating db scheme from version 1 to 2…"); |
|
|
|
|
var list = new ArrayList<Appointment>(); |
|
|
|
|
|
|
|
|
|
connection.setAutoCommit(false); |
|
|
|
|
|
|
|
|
|
Query.of(ADD_SLUG).statement(connection).execute(); |
|
|
|
|
LOG.log(DEBUG, "Adding slug column…"); |
|
|
|
|
connection.prepareStatement(ADD_SLUG).execute(); |
|
|
|
|
var slugMap = new HashMap<Long, String>(); |
|
|
|
|
var rs = Query.of(SELECT_APPOINTMENTS).execute(connection); |
|
|
|
|
LOG.log(DEBUG, "Reading existing appointments…"); |
|
|
|
|
var rs = Query.select("*").from("appointments").exec(connection); |
|
|
|
|
while (rs.next()) { |
|
|
|
|
var id = rs.getLong("aid"); |
|
|
|
|
var location = nullable(rs.getString("location")); |
|
|
|
|
var id = rs.getLong(AID); |
|
|
|
|
var location = nullable(nullIfEmpty(rs.getString("location"))); |
|
|
|
|
if (location.isEmpty()) continue; |
|
|
|
|
var title = rs.getString("title"); |
|
|
|
|
LOG.log(TRACE, () -> "%s: %s".formatted(id, title)); |
|
|
|
|
var descr = rs.getString("description"); |
|
|
|
|
if (allEmpty(title, descr)) continue; |
|
|
|
|
var start = nullable(rs.getTimestamp("start")); |
|
|
|
|
if (start.isEmpty()) continue; |
|
|
|
|
var slug = "%s@%s".formatted(start.get().toLocalDateTime(), camelCase(location.get())); |
|
|
|
|
var slug = "%s@%s".formatted(start.get().toLocalDateTime(), camelCase(location.get().replace(",", ""))); |
|
|
|
|
if (slug.length() > 250) slug = slug.substring(0, 250); |
|
|
|
|
slugMap.put(id, slug); |
|
|
|
|
} |
|
|
|
|
rs.close(); |
|
|
|
|
var stmt = Query.of(INSERT_SLUG).statement(connection); |
|
|
|
|
LOG.log(DEBUG, "Creating slugs…"); |
|
|
|
|
var query = Query.updateIgnore("appointments").set("slug").where(AID, equal(MARK)).prepare(connection); |
|
|
|
|
for (var entry : slugMap.entrySet()) { |
|
|
|
|
stmt.setString(1, entry.getValue()); |
|
|
|
|
stmt.setLong(2, entry.getKey()); |
|
|
|
|
stmt.execute(); |
|
|
|
|
query.apply(entry.getValue(), entry.getKey()); |
|
|
|
|
} |
|
|
|
|
stmt = Query.of(UPDATE_DB_VERSION).statement(connection); |
|
|
|
|
stmt.setLong(1, 2); |
|
|
|
|
stmt.execute(); |
|
|
|
|
|
|
|
|
|
LOG.log(DEBUG, "Writing new db version marker…"); |
|
|
|
|
Query.update("config").set("value").where("keyname", equal("dbversion")).prepare(connection).apply(2); |
|
|
|
|
connection.setAutoCommit(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -94,23 +98,61 @@ public class MariaDB implements Database {
@@ -94,23 +98,61 @@ public class MariaDB implements Database {
|
|
|
|
|
return new MariaDB(DriverManager.getConnection(jdbc, user, pass)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public List<Appointment> list(Integer count, Integer offset) throws SQLException { |
|
|
|
|
var list = new ArrayList<Appointment>(); |
|
|
|
|
var results = Query.of(SELECT_APPOINTMENTS).orderBy("start").execute(connection); |
|
|
|
|
while (results.next()) { |
|
|
|
|
var id = results.getInt("aid"); |
|
|
|
|
var title = results.getString("title"); |
|
|
|
|
var description = results.getString("description"); |
|
|
|
|
if (allEmpty(title, description)) continue; |
|
|
|
|
var start = results.getTimestamp("start").toLocalDateTime(); |
|
|
|
|
var end = nullable(results.getTimestamp("end")).map(Timestamp::toLocalDateTime).orElse(null); |
|
|
|
|
var location = results.getString("location"); |
|
|
|
|
var slug = results.getString("slug"); |
|
|
|
|
if (slug == null) slug = Calc.hash(start + "@" + location).orElse(null); |
|
|
|
|
list.add(new BaseAppointment(id, title, description, start, end, location, slug)); |
|
|
|
|
var results = Query.select("*").from(APPOINTMENTS).sort("start").exec(connection); |
|
|
|
|
while (results.next()) createAppointmentOf(results).optional().ifPresent(list::add); |
|
|
|
|
results.close(); |
|
|
|
|
return list; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public Result<Appointment> loadEvent(long id) { |
|
|
|
|
try { |
|
|
|
|
var rs = Query //
|
|
|
|
|
.select("%s.*".formatted(APPOINTMENTS), "GROUP_CONCAT(keyword) AS tags") |
|
|
|
|
.from(APPOINTMENTS) |
|
|
|
|
.leftJoin(AID, "appointment_tags", AID) |
|
|
|
|
.leftJoin("tid", "tags", "tid") |
|
|
|
|
.groupBy(AID) |
|
|
|
|
.where("%s.%s".formatted(APPOINTMENTS, AID), equal(id)) |
|
|
|
|
.exec(connection); |
|
|
|
|
return rs.next() ? createAppointmentOf(rs) : Error.format("Failed to find appointment with id %s", id); |
|
|
|
|
} catch (SQLException e) { |
|
|
|
|
return Error.of("Failed to load appointment with id = %s".formatted(id), e); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Result<Appointment> createAppointmentOf(ResultSet results) throws SQLException { |
|
|
|
|
var id = results.getInt(AID); |
|
|
|
|
var title = results.getString("title"); |
|
|
|
|
var description = results.getString("description"); |
|
|
|
|
if (allEmpty(title, description)) return Error.format("Title and Description of appointment %s are empty", id); |
|
|
|
|
var start = results.getTimestamp("start").toLocalDateTime(); |
|
|
|
|
var end = nullable(results.getTimestamp("end")).map(Timestamp::toLocalDateTime).orElse(null); |
|
|
|
|
var location = results.getString("location"); |
|
|
|
|
var slug = results.getString("slug"); |
|
|
|
|
var tags = nullIfEmpty(results.getString("tags")); |
|
|
|
|
if (slug == null) slug = Calc.hash(start + "@" + location).orElse(null); |
|
|
|
|
var appointment = new BaseAppointment(id, title, description, start, end, location, slug); |
|
|
|
|
if (tags != null) appointment.tags(tags.split(",")); |
|
|
|
|
return Payload.of(appointment); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public List<Appointment> list(LocalDateTime from, LocalDateTime till) throws SQLException { |
|
|
|
|
var list = new ArrayList<Appointment>(); |
|
|
|
|
var results = Query //
|
|
|
|
|
.select("appointments.*", "GROUP_CONCAT(keyword) AS tags") |
|
|
|
|
.from(APPOINTMENTS) |
|
|
|
|
.leftJoin(AID, "appointment_tags", AID) |
|
|
|
|
.leftJoin("tid", "tags", "tid") |
|
|
|
|
.groupBy(AID) |
|
|
|
|
.sort("start") |
|
|
|
|
.where("start", moreThan(from)) |
|
|
|
|
.exec(connection); |
|
|
|
|
while (results.next()) createAppointmentOf(results).optional().ifPresent(list::add); |
|
|
|
|
results.close(); |
|
|
|
|
return list; |
|
|
|
|
} |
|
|
|
|