package divconq.db.proc;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import divconq.db.TablesAdapter;
import divconq.db.DatabaseInterface;
import divconq.db.DatabaseTask;
import divconq.db.IStoredProc;
import divconq.lang.BigDateTime;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationResult;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.struct.builder.ICompositeBuilder;
import divconq.util.StringUtil;
public class UpdateRecord implements IStoredProc {
@Override
public void execute(DatabaseInterface conn, DatabaseTask task, OperationResult log) {
boolean isUpdate = task.getName().equals("dcUpdateRecord");
RecordStruct params = task.getParamsAsRecord();
String table = params.getFieldAsString("Table");
TablesAdapter db = new TablesAdapter(conn, task);
// ===========================================
// verify the fields
// ===========================================
RecordStruct fields = params.getFieldAsRecord("Fields");
BigDateTime when = params.getFieldAsBigDateTime("When");
if (when == null)
when = BigDateTime.nowDateTime();
if (!task.isReplicating()) {
// only check first time, otherwise allow replication
OperationResult cor = db.checkFields(table, fields, params.getFieldAsString("Id"));
if (cor.hasErrors()) {
task.complete();
return;
}
}
// ===========================================
// run before trigger
// ===========================================
OperationResult cor = db.executeTrigger(table, isUpdate ? "BeforeUpdate" : "BeforeInsert", conn, task, log);
if (cor.hasErrors()) {
task.complete();
return;
}
// it is possible for Id to be set by trigger (e.g. with domains)
String id = params.getFieldAsString("Id");
// TODO add db filter option
//d runFilter("Insert" or "Update") quit:Errors ; if any violations in filter then do not proceed
// ===========================================
// create new id
// ===========================================
// don't create a new id during replication - not even for dcInsertRecord
if (StringUtil.isEmpty(id)) {
FuncResult<String> ires = db.createRecord(table);
if (ires.hasErrors()) {
task.complete();
return;
}
id = ires.getResult();
params.setField("Id", id);
}
// ===========================================
// do the data update
// ===========================================
db.setFields(table, id, fields);
// ===========================================
// and set fields
// ===========================================
// TODO move to tables interface
if (params.hasField("Sets")) {
ListStruct sets = params.getFieldAsList("Sets");
for (Struct set : sets.getItems()) {
RecordStruct rset = (RecordStruct) set;
String field = rset.getFieldAsString("Field");
// make a copy
List<String> lsubids = rset.getFieldAsList("Values").toStringList();
List<String> othersubids = new ArrayList<>();
db.traverseSubIds(table, id, field, when, false, new Consumer<Object>() {
@Override
public void accept(Object msub) {
String suid = msub.toString();
// if a value is already set, don't set it again
if (!lsubids.remove(suid))
othersubids.add(suid);
}
});
// Retire non matches
for (String suid : othersubids) {
// if present in our list then retire it
db.setFields(table, id, new RecordStruct()
.withField(field, new RecordStruct()
.withField(suid, new RecordStruct()
.withField("Retired", true)
)
)
);
}
// add any remaining - unmatched - suids
for (String suid : lsubids) {
// if present in our list then retire it
db.setFields(table, id, new RecordStruct()
.withField(field, new RecordStruct()
.withField(suid, new RecordStruct()
.withField("Data", suid)
)
)
);
}
}
}
// TODO make a record of everything for replication? or just let it figure it out?
// ===========================================
// run after trigger
// ===========================================
cor = db.executeTrigger(table, isUpdate ? "AfterUpdate" : "AfterInsert", conn, task, log);
if (cor.hasErrors()) {
task.complete();
return;
}
// ===========================================
// return results
// ===========================================
// don't bother returning data during replication
if (!isUpdate && !task.isReplicating()) {
ICompositeBuilder resp = task.getBuilder();
try {
resp.startRecord();
resp.field("Id", id);
resp.endRecord();
/* alternative solution
RecordStruct rec = new RecordStruct(new FieldStruct("Id", id));
rec.toBuilder(resp);
*/
}
catch (Exception x) {
log.error("UpdateRecord: Unable to create response: " + x);
}
}
task.complete();
}
}