package divconq.db.proc; import java.util.function.Consumer; import divconq.db.IComposer; import divconq.db.TablesAdapter; import divconq.db.DatabaseInterface; import divconq.db.DatabaseTask; import divconq.db.IStoredProc; import divconq.hub.Hub; import divconq.lang.BigDateTime; import divconq.lang.op.OperationResult; import divconq.schema.DbComposer; import divconq.schema.DbField; import divconq.struct.FieldStruct; import divconq.struct.ListStruct; import divconq.struct.RecordStruct; import divconq.struct.Struct; import divconq.struct.builder.ICompositeBuilder; import divconq.util.StringUtil; public class LoadRecord implements IStoredProc { @Override public void execute(DatabaseInterface conn, DatabaseTask task, OperationResult log) { RecordStruct params = task.getParamsAsRecord(); String table = params.getFieldAsString("Table"); String id = params.getFieldAsString("Id"); BigDateTime when = params.getFieldAsBigDateTime("When"); boolean compact = params.hasField("Compact") ? params.getFieldAsBooleanOrFalse("Compact") : true; boolean historical = params.getFieldAsBooleanOrFalse("Historical"); ListStruct select = params.getFieldAsList("Select"); if (when == null) when = BigDateTime.nowDateTime(); // TODO add db filter option //d runFilter("Query") quit:Errors ; if any violations in filter then do not proceed TablesAdapter db = new TablesAdapter(conn, task); ICompositeBuilder out = task.getBuilder(); try { this.writeRecord(conn, task, log, out, db, table, id, when, select, compact, false, historical); } catch (Exception x) { log.error("Issue with loading record: " + x); } task.complete(); } public void writeRecord(DatabaseInterface conn, DatabaseTask task, OperationResult log, ICompositeBuilder out, TablesAdapter db, String table, String id, BigDateTime when, ListStruct select, boolean compact, boolean skipWriteRec, boolean historical) throws Exception { if (!db.isCurrent(table, id, when, historical)) return; // if select none then select all if (select.getSize() == 0) { for (DbField entry : task.getSchema().getDbFields(table)) select.addItem(new RecordStruct(new FieldStruct("Field", entry.getName()))); } if (!skipWriteRec) out.startRecord(); for (Struct s : select.getItems()) { RecordStruct fld = (RecordStruct) s; out.field(fld.isFieldEmpty("Name") ? fld.getFieldAsString("Field") : fld.getFieldAsString("Name")); this.writeField(conn, task, log, out, db, table, id, when, fld, historical, compact); } if (!skipWriteRec) out.endRecord(); } public void writeField(DatabaseInterface conn, DatabaseTask task, OperationResult log, ICompositeBuilder out, TablesAdapter db, String table, String id, BigDateTime when, String field, boolean historical, boolean compact) throws Exception { RecordStruct fld = new RecordStruct() .withField("Field", field); this.writeField(conn, task, log, out, db, table, id, when, fld, historical, compact); } public void writeField(DatabaseInterface conn, DatabaseTask task, OperationResult log, ICompositeBuilder out, TablesAdapter db, String table, String id, BigDateTime when, RecordStruct field, boolean historical, boolean compact) throws Exception { // some fields may request full details even if query is not in general if (compact && field.getFieldAsBooleanOrFalse("Full")) compact = false; boolean myCompact = compact; if (!field.isFieldEmpty("Composer")) { DbComposer proc = Hub.instance.getSchema().getDbComposer(field.getFieldAsString("Composer")); if (proc == null) { out.value(null); return; } IComposer sp = proc.getComposer(); if (sp == null) out.value(null); else sp.writeField(conn, task, log, out, db, table, id, when, field, historical, myCompact); return; } if (!field.isFieldEmpty("Value")) { out.value(field.getFieldAsAny("Value")); return; } String fname = field.getFieldAsString("Field"); String format = field.getFieldAsString("Format"); DbField fdef = task.getSchema().getDbField(table, fname); if (fdef == null) { out.value(null); return; } // for foreign key queries String ftable = fdef.getForeignKey(); // for reverse foreign key queries if (!field.isFieldEmpty("Table")) ftable = field.getFieldAsString("Table"); String fktable = ftable; // when set, subselect indicates that we want values from a foreign field ListStruct subselect = StringUtil.isNotEmpty(fktable) ? this.buildSubquery(field, fktable) : null; // when set, sfield indicates we want the foreign value inline with the other values RecordStruct sfield = ((subselect != null) && (subselect.getSize() == 1)) ? subselect.getItemAsRecord(0) : null; Consumer<Object> foreignSink = new Consumer<Object>() { @Override public void accept(Object fid) { try { if (fid == null) out.value(null); else if (sfield != null) // if a single field the write out the field out "inlined" LoadRecord.this.writeField(conn, task, log, out, db, fktable, fid.toString(), when, sfield, historical, myCompact); else // otherwise write the field out as a record within the list LoadRecord.this.writeRecord(conn, task, log, out, db, fktable, fid.toString(), when, subselect, myCompact, false, historical); } catch (Exception x) { log.error("Unable to write foreign record: " + x); } } }; if ("Id".equals(fname)) { // keep in mind that `id` is the "value" if ((subselect == null) || field.isFieldEmpty("KeyField")) { if (compact) out.value(id); else out.startRecord().field("Data", id).endRecord(); } else { // write all records in reverse index within a List out.startList(); db.traverseIndex(fktable, field.getFieldAsString("KeyField"), id, when, historical, foreignSink); out.endList(); } return; } String subid = field.getFieldAsString("SubId"); if (StringUtil.isNotEmpty(subid) && fdef.isList()) { if (subselect != null) foreignSink.accept(db.getDynamicList(table, id, fname, subid, when, format)); else if (compact) out.value(db.getDynamicList(table, id, fname, subid, when, format)); else out.value(db.getDynamicListExtended(table, id, fname, subid, when, format)); } // DynamicList, StaticList (or DynamicScalar is when == null) else if (fdef.isList() || (fdef.isDynamic() && when == null)) { out.startList(); // keep in mind that `id` is the "value" in the index db.traverseSubIds(table, id, fname, when, historical, new Consumer<Object>() { @Override public void accept(Object subid) { try { // don't output null values in this list - Extended might return null data but otherwise no nulls if (subselect != null) { Object value = db.getDynamicList(table, id, fname, subid.toString(), when, format); if (value != null) foreignSink.accept(value); } else if (myCompact) { Object value = db.getDynamicList(table, id, fname, subid.toString(), when); if (value != null) out.value(value); } else { Object value = db.getDynamicListExtended(table, id, fname, subid.toString(), when, format); if (value != null) out.value(value); } } catch (Exception x) { log.error("Unable to write subid: " + x); } } }); out.endList(); return; } // DynamicScalar else if (fdef.isDynamic()) { if (subselect != null) foreignSink.accept(db.getDynamicScalar(table, id, fname, when, format, historical)); else if (compact) out.value(db.getDynamicScalar(table, id, fname, when, format, historical)); else out.value(db.getDynamicScalarExtended(table, id, fname, when, format, historical)); } // StaticScalar else { if (subselect != null) foreignSink.accept(db.getStaticScalar(table, id, fname, format)); else if (compact) out.value(db.getStaticScalar(table, id, fname, format)); else out.value(db.getStaticScalarExtended(table, id, fname, format)); } return; } // this works with FK queries (field and ftable params) and with ID Reverse query (just field param) public ListStruct buildSubquery(RecordStruct field, String ftable) { if (StringUtil.isEmpty(ftable)) return null; ListStruct subselect = field.getFieldAsList("Select"); // if no subquery then use "ForeignField" instead if ((subselect == null) || (subselect.getSize() == 0)) { if (field.hasField("ForeignField")) return new ListStruct(new RecordStruct( new FieldStruct("Field", field.getFieldAsString("ForeignField")), new FieldStruct("Format", field.getFieldAsString("Format")) )); // TODO if no ForeignField then select all return null; } return subselect; } /* ; ; format(table,field,val,format) i (table'["#")&(Domain'="") s table=table_"#"_Domain ; support table instances quit:format="" $$getTypeFor(^dcSchema($p(table,"#"),"Fields",field,"Type"))_val quit:format="Tr" ScalarStr_$$tr^dcStrUtil("_enum_"_table_"_"_field_"_"_val) ; TODO support date and number formatting, maybe str padding quit ScalarStr_$$format^dcStrUtil(val,format) ; ; ; getTypeFor(type) quit:type="Time" ScalarTime quit:type="Date" ScalarDate quit:type="DateTime" ScalarDateTime quit:type="Id" ScalarId quit:type="Integer" ScalarInt quit:type="Json" ScalarJson quit:type="Decimal" ScalarDec quit:type="BigInteger" ScalarBigInt quit:type="BigDecimal" ScalarBigDec quit:type="Number" ScalarNum quit:type="Boolean" ScalarBool quit:type="Binary" ScalarBin quit:type="BigDateTime" ScalarBigDateTime quit ScalarStr ; ; */ }