|
|
|
@@ -4,9 +4,9 @@ package de.srsoftware.cal.db;
|
|
|
|
|
import static de.srsoftware.cal.Util.extractCoords;
|
|
|
|
|
import static de.srsoftware.cal.db.Fields.*;
|
|
|
|
|
import static de.srsoftware.cal.db.Fields.ALL;
|
|
|
|
|
import static de.srsoftware.tools.Error.error;
|
|
|
|
|
import static de.srsoftware.tools.container.Error.error;
|
|
|
|
|
import static de.srsoftware.tools.Optionals.*;
|
|
|
|
|
import static de.srsoftware.tools.Result.transform;
|
|
|
|
|
import static de.srsoftware.tools.container.Container.transform;
|
|
|
|
|
import static de.srsoftware.tools.jdbc.Condition.*;
|
|
|
|
|
import static de.srsoftware.tools.jdbc.Query.*;
|
|
|
|
|
import static java.lang.System.Logger.Level.*;
|
|
|
|
@@ -15,8 +15,8 @@ import de.srsoftware.cal.BaseAppointment;
|
|
|
|
|
import de.srsoftware.cal.api.Appointment;
|
|
|
|
|
import de.srsoftware.cal.api.Attachment;
|
|
|
|
|
import de.srsoftware.cal.api.Link;
|
|
|
|
|
import de.srsoftware.tools.Payload;
|
|
|
|
|
import de.srsoftware.tools.Result;
|
|
|
|
|
import de.srsoftware.tools.container.Payload;
|
|
|
|
|
import de.srsoftware.tools.container.Container;
|
|
|
|
|
import de.srsoftware.tools.jdbc.Query;
|
|
|
|
|
import java.net.MalformedURLException;
|
|
|
|
|
import java.net.URI;
|
|
|
|
@@ -38,10 +38,12 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
private static final String TAG_SEARCH = "tag_search";
|
|
|
|
|
private static final String URLS = "urls";
|
|
|
|
|
private static final String VALUE = "value";
|
|
|
|
|
private final Dialect dialect;
|
|
|
|
|
protected Connection connection;
|
|
|
|
|
|
|
|
|
|
protected SqlDb(Connection conn) throws SQLException {
|
|
|
|
|
protected SqlDb(Connection conn, Dialect dialect) throws SQLException {
|
|
|
|
|
connection = conn;
|
|
|
|
|
this.dialect = dialect;
|
|
|
|
|
applyUpdates();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -107,7 +109,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<Appointment> add(Appointment appointment) {
|
|
|
|
|
public Container<Appointment> add(Appointment appointment) {
|
|
|
|
|
try {
|
|
|
|
|
var start = Timestamp.valueOf(appointment.start());
|
|
|
|
|
var end = appointment.end().map(Timestamp::valueOf).orElse(null);
|
|
|
|
@@ -142,7 +144,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
if (assignQuery == null) assignQuery = insertInto(APPOINTMENT_ATTACHMENTS, AID, UID, MIME);
|
|
|
|
|
if (urlId.isPresent()) assignQuery.values(saved.id(), urlId.get(), attachment.mime());
|
|
|
|
|
}
|
|
|
|
|
if (assignQuery != null) assignQuery.ignoreDuplicates().execute(connection).close();
|
|
|
|
|
if (assignQuery != null) assignQuery.ignoreDuplicates(dialect).execute(connection).close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void writeLinks(Appointment saved) throws SQLException { // link to links
|
|
|
|
@@ -153,7 +155,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
if (assignQuery == null) assignQuery = insertInto(APPOINTMENT_URLS, AID, UID, DESCRIPTION);
|
|
|
|
|
if (urlId.isPresent()) assignQuery.values(saved.id(), urlId.get(), link.desciption());
|
|
|
|
|
}
|
|
|
|
|
if (assignQuery != null) assignQuery.ignoreDuplicates().execute(connection).close();
|
|
|
|
|
if (assignQuery != null) assignQuery.ignoreDuplicates(dialect).execute(connection).close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void writeTags(Appointment saved) throws SQLException {
|
|
|
|
@@ -164,7 +166,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
if (assignQuery == null) assignQuery = insertInto(APPOINTMENT_TAGS, AID, TID);
|
|
|
|
|
if (tagId.isPresent()) assignQuery.values(saved.id(), tagId.get());
|
|
|
|
|
}
|
|
|
|
|
if (assignQuery != null) assignQuery.ignoreDuplicates().execute(connection).close();
|
|
|
|
|
if (assignQuery != null) assignQuery.ignoreDuplicates(dialect).execute(connection).close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Optional<Long> getOrCreateUrl(URL url) throws SQLException {
|
|
|
|
@@ -199,7 +201,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<List<String>> findTags(String infix) {
|
|
|
|
|
public Container<List<String>> findTags(String infix) {
|
|
|
|
|
try {
|
|
|
|
|
List<String> results = new ArrayList<>();
|
|
|
|
|
var rs = select(KEYWORD).from(TAGS).where(KEYWORD, like("%%%s%%".formatted(infix))).sort(KEYWORD).exec(connection);
|
|
|
|
@@ -213,7 +215,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<List<Appointment>> list(LocalDateTime from, LocalDateTime till, Integer count, Integer offset, Collection<String> tags) {
|
|
|
|
|
public Container<List<Appointment>> list(LocalDateTime from, LocalDateTime till, Integer count, Integer offset, Collection<String> tags) {
|
|
|
|
|
List<Long> aids = null;
|
|
|
|
|
if (tags != null && !tags.isEmpty()){
|
|
|
|
|
var query = select(AID).from(TAG_SEARCH);
|
|
|
|
@@ -233,7 +235,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
.from(APPOINTMENTS)
|
|
|
|
|
.leftJoin(AID, "appointment_tags", AID)
|
|
|
|
|
.leftJoin("tid", "tags", "tid")
|
|
|
|
|
.groupBy(AID)
|
|
|
|
|
.groupBy(APPOINTMENTS+"."+AID)
|
|
|
|
|
.sort("start ASC");
|
|
|
|
|
if (aids != null && !aids.isEmpty()) query.where(APPOINTMENTS+"."+AID,in(aids.toArray()));
|
|
|
|
|
if (from != null) query.where(START, moreThan(Timestamp.valueOf(from)));
|
|
|
|
@@ -284,10 +286,10 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<Appointment> loadEvent(long id) {
|
|
|
|
|
public Container<Appointment> loadEvent(long id) {
|
|
|
|
|
try {
|
|
|
|
|
var rs = select(ALL).from(APPOINTMENTS).where(AID, equal(id)).exec(connection);
|
|
|
|
|
Result<Appointment> result = rs.next() ? createAppointmentOf(rs).map(this::loadExtra) : NotFound.of("Failed to find appointment with id %s", id);
|
|
|
|
|
Container<Appointment> result = rs.next() ? createAppointmentOf(rs).map(this::loadExtra) : NotFound.of("Failed to find appointment with id %s", id);
|
|
|
|
|
rs.getStatement().close();
|
|
|
|
|
rs.close();
|
|
|
|
|
return result;
|
|
|
|
@@ -297,10 +299,10 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<Appointment> loadEvent(String location, LocalDateTime start) {
|
|
|
|
|
public Container<Appointment> loadEvent(String location, LocalDateTime start) {
|
|
|
|
|
try {
|
|
|
|
|
var rs = select(ALL).from(APPOINTMENTS).where(LOCATION, equal(location)).where(START, equal(Timestamp.valueOf(start))).exec(connection);
|
|
|
|
|
Result<Appointment> result = rs.next() ? createAppointmentOf(rs).map(this::loadExtra) : error("Failed to find appointment starting %s @ %s", start, location);
|
|
|
|
|
Container<Appointment> result = rs.next() ? createAppointmentOf(rs).map(this::loadExtra) : error("Failed to find appointment starting %s @ %s", start, location);
|
|
|
|
|
rs.getStatement().close();
|
|
|
|
|
rs.close();
|
|
|
|
|
return result;
|
|
|
|
@@ -309,11 +311,11 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Result<Appointment> loadExtra(Result<BaseAppointment> res) {
|
|
|
|
|
private Container<Appointment> loadExtra(Container<BaseAppointment> res) {
|
|
|
|
|
return loadTags(res).map(this::loadLinks).map(this::loadAttachments);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Result<BaseAppointment> loadTags(Result<BaseAppointment> res) {
|
|
|
|
|
private Container<BaseAppointment> loadTags(Container<BaseAppointment> res) {
|
|
|
|
|
if (res.optional().isEmpty()) return transform(res);
|
|
|
|
|
BaseAppointment event = res.optional().get();
|
|
|
|
|
var id = event.id();
|
|
|
|
@@ -328,7 +330,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Result<BaseAppointment> loadLinks(Result<BaseAppointment> res) {
|
|
|
|
|
private Container<BaseAppointment> loadLinks(Container<BaseAppointment> res) {
|
|
|
|
|
if (res.optional().isEmpty()) return transform(res);
|
|
|
|
|
BaseAppointment event = res.optional().get();
|
|
|
|
|
var id = event.id();
|
|
|
|
@@ -352,7 +354,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Result<Appointment> loadAttachments(Result<BaseAppointment> res) {
|
|
|
|
|
private Container<Appointment> loadAttachments(Container<BaseAppointment> res) {
|
|
|
|
|
if (res.optional().isEmpty()) return transform(res);
|
|
|
|
|
BaseAppointment event = res.optional().get();
|
|
|
|
|
var id = event.id();
|
|
|
|
@@ -376,7 +378,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Result<BaseAppointment> createAppointmentOf(ResultSet results) throws SQLException {
|
|
|
|
|
private Container<BaseAppointment> createAppointmentOf(ResultSet results) throws SQLException {
|
|
|
|
|
var id = results.getInt(AID);
|
|
|
|
|
var title = results.getString(TITLE);
|
|
|
|
|
var description = results.getString(DESCRIPTION);
|
|
|
|
@@ -407,7 +409,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<Long> removeAppointment(long id) {
|
|
|
|
|
public Container<Long> removeAppointment(long id) {
|
|
|
|
|
try {
|
|
|
|
|
delete().from(APPOINTMENTS).where(AID, equal(id)).execute(connection);
|
|
|
|
|
delete().from(APPOINTMENT_TAGS).where(AID, equal(id)).execute(connection);
|
|
|
|
@@ -420,7 +422,7 @@ public abstract class SqlDb implements Database {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<Appointment> update(Appointment event) {
|
|
|
|
|
public Container<Appointment> update(Appointment event) {
|
|
|
|
|
var start = Timestamp.valueOf(event.start());
|
|
|
|
|
var end = event.end().map(Timestamp::valueOf).orElse(null);
|
|
|
|
|
var coords = event.coords().map(Object::toString).orElse(null);
|
|
|
|
|