package divconq.db;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import divconq.db.util.ByteUtil;
import divconq.hub.DomainInfo;
import divconq.hub.Hub;
import divconq.lang.BigDateTime;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.schema.DbField;
import divconq.schema.DbTable;
import divconq.schema.DbTrigger;
import divconq.struct.FieldStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.util.StringUtil;
import static divconq.db.Constants.*;
public class TablesAdapter {
protected DatabaseInterface conn = null;
protected DatabaseTask task = null;
// don't call for general code...
public TablesAdapter(DatabaseInterface conn, DatabaseTask task) {
this.conn = conn;
this.task = task;
}
public FuncResult<String> createRecord(String table) {
FuncResult<String> or = new FuncResult<String>();
String hid = OperationContext.getHubId();
byte[] metakey = ByteUtil.buildKey(DB_GLOBAL_RECORD_META, table, "Id", hid);
// use common Id's across all domains so that merge works and so that
// sys domain records (users) can be reused across domains
try {
Long id = this.conn.inc(metakey);
or.setResult(hid + "_" + StringUtil.leftPad(id.toString(), 15, '0'));
}
catch (Exception x) {
or.error("Unable to create record id: " + x);
}
return or;
}
public OperationResult checkFields(String table, RecordStruct fields, String inId) {
OperationResult or = new OperationResult();
BiConsumer<DbField,RecordStruct> fieldChecker = new BiConsumer<DbField,RecordStruct>() {
@Override
public void accept(DbField schema, RecordStruct data) {
boolean retired = data.getFieldAsBooleanOrFalse("Retired");
if (retired) {
if (schema.isRequired())
OperationContext.get().error("Field cannot be retired: " + table + " - " + schema.getName());
return;
}
// validate data type
Struct value = data.getField("Data");
if (value == null) {
if (schema.isRequired())
OperationContext.get().error("Field cannot be null: " + table + " - " + schema.getName());
return;
}
/*
OperationResult cor = TablesAdapter.this.task.getSchema().validateType(value, schema.getTypeId());
if (cor.hasErrors())
return;
*/
FuncResult<Struct> cor = TablesAdapter.this.task.getSchema().normalizeValidateType(value, schema.getTypeId());
if (cor.hasErrors())
return;
data.setField("Data", cor.getResult());
Object cValue = Struct.objectToCore(value);
if (cValue == null) {
if (schema.isRequired())
OperationContext.get().error("Field cannot be null: " + table + " - " + schema.getName());
return;
}
if (!schema.isUnique())
return;
// make sure value is unique - null for when is fine because uniqueness is not time bound
Object id = TablesAdapter.this.firstInIndex(table, schema.getName(), cValue, null, false);
// if we are a new record
if (inId == null) {
if (id != null) {
OperationContext.get().error("Field must be unique: " + table + " - " + schema.getName());
return;
}
}
// if we are not a new record
else if (id != null) {
if (!inId.equals(id)) {
OperationContext.get().error("Field already in use, must be unique: " + table + " - " + schema.getName());
return;
}
}
}
};
// checking incoming fields for type correctness, uniqueness and requiredness
for (FieldStruct field : fields.getFields()) {
String fname = field.getName();
try {
DbField schema = this.task.getSchema().getDbField(table, fname);
if (schema == null) {
OperationContext.get().error("Field not defined: " + table + " - " + fname);
continue;
}
// --------------------------------------
// StaticScalar handling - Data or Retired (true) not both
// --------------------------------------
if (!schema.isList() && !schema.isDynamic()) {
fieldChecker.accept(schema, (RecordStruct) field.getValue());
}
// --------------------------------------
// StaticList handling
// DynamicScalar handling
// DynamicList handling
// --------------------------------------
else {
for (FieldStruct subid : ((RecordStruct) field.getValue()).getFields())
fieldChecker.accept(schema, (RecordStruct) subid.getValue());
}
}
catch (Exception x) {
or.error("Error checking field: " + fname);
}
}
// if we are a new record, check that we have all the required fields
if (inId == null) {
for (DbField schema : this.task.getSchema().getDbFields(table)) {
if (!schema.isRequired())
continue;
// all we need to do is check if the field is present, the checks above have already shown
// that fields present pass the required check
if (!fields.hasField(schema.getName()))
OperationContext.get().error("Field missing but required: " + table + " - " + schema.getName());
}
}
return or;
}
public OperationResult checkSetFields(String table, String id, RecordStruct fields) {
OperationResult cor = this.checkFields(table, fields, id);
if (cor.hasErrors())
return cor;
return this.setFields(table, id, fields);
}
public OperationResult setFields(String table, String id, RecordStruct fields) {
OperationResult or = new OperationResult();
boolean auditDisabled = this.conn.isAuditDisabled();
BigDecimal stamp = this.task.getStamp();
String did = this.task.getDomain();
try {
boolean recPresent = this.conn.hasAny(DB_GLOBAL_RECORD, did, table, id);
if (!recPresent) {
byte[] metacnt = ByteUtil.buildKey(DB_GLOBAL_RECORD_META, did, table, "Count");
// update the record count
this.conn.inc(metacnt);
}
}
catch (Exception x) {
or.error("Unable to check/update record count: " + x);
return or;
}
// not just retired, but truly deleted - then proceed no further
// could hit a race condition - DB cleanup will look for "deleted" records with data and remove TODO
if (this.isDeleted(table, id)) {
or.errorTr(50009);
return or;
}
// --------------------------------------------------
// index updates herein may have race condition issues with the value counts
// this is ok as counts are just used for suggestions anyway
// TODO - DB cleanup on all indexes
// TODO - DB cleanup on all index value counts
// --------------------------------------------------
for (FieldStruct field : fields.getFields()) {
String fname = field.getName();
try {
DbField schema = this.task.getSchema().getDbField(table, fname);
if (schema == null) {
OperationContext.get().error("Field not defined: " + table + " - " + fname);
continue;
}
// --------------------------------------
// StaticScalar handling - Data or Retired (true) not both
//
// fields([field name],"Data") = [value]
// fields([field name],"Tags") = [value]
// fields([field name],"Retired") = [value]
// --------------------------------------
if (!schema.isList() && !schema.isDynamic()) {
RecordStruct data = (RecordStruct) field.getValue();
Object newValue = Struct.objectToCore(data.getField("Data"));
String tags = data.getFieldAsString("Tags");
boolean retired = data.getFieldAsBooleanOrFalse("Retired");
boolean updateOnly = data.getFieldAsBooleanOrFalse("UpdateOnly");
// find the first, newest, stamp
byte[] newerStamp = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, fname, null);
boolean hasNewer = false;
if (newerStamp != null) {
BigDecimal newStamp = Struct.objectToDecimal(ByteUtil.extractValue(newerStamp));
hasNewer = stamp.compareTo(newStamp) > 0; // if we come after newer then we are older info, there is newer
}
// find the next, older, stamp after current
byte[] olderStamp = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, fname, stamp);
boolean oldIsSet = false;
boolean oldIsRetired = false;
Object oldValue = null;
if (olderStamp != null) {
BigDecimal oldStamp = Struct.objectToDecimal(ByteUtil.extractValue(olderStamp));
// try to get the data if any - note retired fields have no data
if (oldStamp != null) {
oldIsRetired = this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, did, table, id, fname, oldStamp, "Retired");
oldIsSet = this.conn.isSet(DB_GLOBAL_RECORD, did, table, id, fname, oldStamp, "Data");
if (oldIsSet)
oldValue = this.conn.get(DB_GLOBAL_RECORD, did, table, id, fname, oldStamp, "Data");
}
}
boolean effectivelyEqual = (retired && oldIsRetired) || ((oldValue == null) && (newValue == null)) || ((oldValue != null) && oldValue.equals(newValue));
if (updateOnly && effectivelyEqual)
continue;
// set either retired or data, not both
if (retired) {
if (oldIsSet && auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, stamp, "Data");
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, stamp, "Retired", retired);
}
else {
if (auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, stamp, "Retired");
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, stamp, "Data", newValue);
}
// add tags if any - ok even if retired
if (StringUtil.isNotEmpty(tags))
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, stamp, "Tags", tags);
else if (auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, stamp, "Tags");
// don't bother with the indexes if not configured
// or if there is a newer value for this field already set
if (!schema.isIndexed() || hasNewer || effectivelyEqual)
continue;
if (oldIsSet && !oldIsRetired) {
if (oldValue instanceof String)
oldValue = oldValue.toString().trim().toLowerCase(Locale.ROOT);
// decrement index count for the old value
// remove the old index value
this.conn.dec(DB_GLOBAL_INDEX, did, table, fname, oldValue);
this.conn.kill(DB_GLOBAL_INDEX, did, table, fname, oldValue, id);
}
if (!retired) {
if (newValue instanceof String)
newValue = newValue.toString().trim().toLowerCase(Locale.ROOT);
// increment index count
// set the new index new
this.conn.inc(DB_GLOBAL_INDEX, did, table, fname, newValue);
this.conn.set(DB_GLOBAL_INDEX, did, table, fname, newValue, id, null);
}
continue;
}
// --------------------------------------
//
// Handling for other types
//
// StaticList handling
// fields([field name],sid,"Data",0) = [value]
// fields([field name],sid,"Tags") = [value] |value1|value2|etc...
// fields([field name],sid,"Retired") = [value] |value1|value2|etc...
//
// DynamicScalar handling
// fields([field name],sid,"Data",0) = [value]
// fields([field name],sid,"From") = [value] null means always was
// fields([field name],sid,"Tags") = [value]
// fields([field name],sid,"Retired") = [value] |value1|value2|etc...
//
// DynamicList handling
// fields([field name],sid,"Data",0) = [value]
// fields([field name],sid,"From") = [value] null means always was
// fields([field name],sid,"To") = [value] null means always will be
// fields([field name],sid,"Tags") = [value]
// fields([field name],sid,"Retired") = [value] |value1|value2|etc...
// --------------------------------------
for (FieldStruct subid : ((RecordStruct) field.getValue()).getFields()) {
String sid = subid.getName();
RecordStruct data = (RecordStruct) subid.getValue();
Object newValue = Struct.objectToCore(data.getField("Data"));
String tags = data.getFieldAsString("Tags");
boolean retired = data.getFieldAsBooleanOrFalse("Retired");
boolean updateOnly = data.getFieldAsBooleanOrFalse("UpdateOnly");
BigDateTime from = data.getFieldAsBigDateTime("From");
BigDateTime to = data.getFieldAsBigDateTime("To");
// find the first, newest, stamp
byte[] newerStamp = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, fname, sid, null);
boolean hasNewer = false;
if (newerStamp != null) {
BigDecimal newStamp = Struct.objectToDecimal(ByteUtil.extractValue(newerStamp));
hasNewer = stamp.compareTo(newStamp) > 0; // if we come after newer then we are older info, there is newer
}
// find the next, older, stamp after current
byte[] olderStamp = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp);
BigDecimal oldStamp = null;
boolean oldIsSet = false;
boolean oldIsRetired = false;
Object oldValue = null;
if (olderStamp != null) {
oldStamp = Struct.objectToDecimal(ByteUtil.extractValue(olderStamp));
// try to get the data if any - note retired fields have no data
if (oldStamp != null) {
oldIsRetired = this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, did, table, id, fname, sid, oldStamp, "Retired");
oldIsSet = this.conn.isSet(DB_GLOBAL_RECORD, did, table, id, fname, sid, oldStamp, "Data");
if (oldIsSet)
oldValue = this.conn.get(DB_GLOBAL_RECORD, did, table, id, fname, sid, oldStamp, "Data");
}
}
boolean effectivelyEqual = (retired && oldIsRetired) || ((oldValue == null) && (newValue == null)) || ((oldValue != null) && oldValue.equals(newValue));
if (updateOnly && effectivelyEqual)
// TODO for dynamic scalar (only) look at previous value (different sid) and skip if that has same value
continue;
// set either retired or data, not both
if (retired) {
// if we are retiring then get rid of old value
if (auditDisabled && oldIsSet)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "Data");
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "Retired", retired);
}
else {
// if we are not retiring then get rid of old Retired just in case it was set before
if (auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "Retired");
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "Data", newValue);
}
// add tags if any - ok even if retired
if (StringUtil.isNotEmpty(tags))
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "Tags", tags);
else if (auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "Tags");
if (from != null)
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "From", from);
else if (auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "From");
if (to != null)
this.conn.set(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "To", to);
else if (auditDisabled)
this.conn.kill(DB_GLOBAL_RECORD, did, table, id, fname, sid, stamp, "To");
// don't bother with the indexes if not configured
// or if there is a newer value for this field already set
if (!schema.isIndexed() || hasNewer || effectivelyEqual)
continue;
if (oldIsSet && !oldIsRetired) {
if (oldValue instanceof String)
oldValue = oldValue.toString().trim().toLowerCase(Locale.ROOT);
// decrement index count for the old value
// remove the old index value
this.conn.dec(DB_GLOBAL_INDEX_SUB, did, table, fname, oldValue);
this.conn.kill(DB_GLOBAL_INDEX_SUB, did, table, fname, oldValue, id, sid);
}
if (!retired) {
if (newValue instanceof String)
newValue = newValue.toString().trim().toLowerCase(Locale.ROOT);
String range = null;
if (from != null)
range = from.toString();
if (to != null) {
if (range == null)
range = ":" + to.toString();
else
range += ":" + to.toString();
}
// increment index count
// set the new index new
this.conn.inc(DB_GLOBAL_INDEX_SUB, did, table, fname, newValue);
this.conn.set(DB_GLOBAL_INDEX_SUB, did, table, fname, newValue, id, sid, range);
}
continue;
}
}
catch (Exception x) {
or.error("Error updating field: " + fname);
}
}
return or;
}
public OperationResult setStaticScalar(String table, String id, String field, Object data) {
RecordStruct fields = new RecordStruct(
new FieldStruct(field, new RecordStruct(
new FieldStruct("Data", data)
))
);
return this.setFields(table, id, fields);
}
public OperationResult setStaticList(String table, String id, String field, String subid, Object data) {
RecordStruct fields = new RecordStruct(
new FieldStruct(field, new RecordStruct(
new FieldStruct(subid,
new RecordStruct(new FieldStruct("Data", data))
)
))
);
return this.setFields(table, id, fields);
}
// from is ms since 1970
public OperationResult setDynamicScalar(String table, String id, String field, String subid, BigDateTime from, Object data) {
RecordStruct fields = new RecordStruct(
new FieldStruct(field, new RecordStruct(
new FieldStruct(subid,
new RecordStruct(
new FieldStruct("Data", data),
new FieldStruct("From", from)
)
)
))
);
return this.setFields(table, id, fields);
}
// from and to are ms since 1970
public OperationResult setDynamicList(String table, String id, String field, String subid, BigDateTime from, BigDateTime to, Object data) {
RecordStruct fields = new RecordStruct(
new FieldStruct(field, new RecordStruct(
new FieldStruct(subid,
new RecordStruct(
new FieldStruct("Data", data),
new FieldStruct("From", from),
new FieldStruct("To", to)
)
)
))
);
return this.setFields(table, id, fields);
}
/*
public void rebiuldIndex(String table) {
;
; don't run when Java connector is active
indexTable(table) n field
f s field=$o(^dcSchema($p(table,"#"),"Fields",field)) q:field="" d indexField(table,field)
quit
;
; don't run when Java connector is active
indexField(table,field) n val,id,stamp,sid,fschema
i (table'["#")&(Domain'="") s table=table_"#"_Domain ; support table instances
;
m fschema=^dcSchema($p(table,"#"),"Fields",field)
;
quit:'fschema("Indexed")
;
i 'fschema("List")&'fschema("Dynamic") k ^dcIndex1(table,field)
e k ^dcIndex2(table,field)
;
f s id=$o(^dcRecord(table,id)) q:id="" d
. i 'fschema("List")&'fschema("Dynamic") d q
. . s stamp=$o(^dcRecord(table,id,field,""),-1) q:stamp=""
. . s val=^dcRecord(table,id,field,stamp,"Data",0)
. . s val=$$val2Ndx(val)
. . ;
. . ; don't index null
. . i val="" q
. . ;
. . s ^dcIndex1(table,field,val,id)=1
. . s ^dcIndex1(table,field,val)=^dcIndex1(table,field,val)+1
. ;
. f s sid=$o(^dcRecord(table,id,field,sid),-1) q:sid="" d
. . s stamp=$o(^dcRecord(table,id,field,sid,""),-1) q:stamp=""
. . ;
. . s val=^dcRecord(table,id,field,sid,stamp,"Data",0)
. . s val=$$val2Ndx(val)
. . ;
. . ; don't index null
. . i val="" q
. . ;
. . s ^dcIndex2(table,field,val,id,sid)=1
. . s ^dcIndex2(table,field,val)=^dcIndex2(table,field,val)+1
;
quit
;
}
*/
public boolean isDeleted(String table, String id) {
try {
return this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, task.getDomain(), table, id, "Deleted");
}
catch (DatabaseException x) {
// TODO logger
}
return false;
}
public boolean isRetired(String table, String id) {
try {
if (!this.conn.hasAny(DB_GLOBAL_RECORD, task.getDomain(), table, id))
return true;
if (this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, task.getDomain(), table, id, "Deleted"))
return true;
if (Struct.objectToBooleanOrFalse(this.getStaticScalar(table, id, "Retired")))
return true;
}
catch (DatabaseException x) {
// TODO logger
}
return false;
}
/*
; check not only retired, but if this record was active during the period of time
; indicated by "when". If a record has no From then it is considered to be
; active indefinitely in the past, prior to To. If there is no To then record
; is active current and since From.
*/
public boolean isCurrent(String table, String id, BigDateTime when, boolean historical) {
if (this.isRetired(table, id))
return false;
if (when == null)
return true;
if (!historical) {
BigDateTime to = Struct.objectToBigDateTime(this.getStaticScalar(table, id, "To"));
// when must come before to
if ((to != null) && (when.compareTo(to) != -1))
return false;
}
BigDateTime from = Struct.objectToBigDateTime(this.getStaticScalar(table, id, "From"));
// when must come after - or at - from
if ((from != null) && (when.compareTo(from) >= 0))
return false;
return true;
}
public Object getStaticScalar(String table, String id, String field) {
return this.getStaticScalar(table, id, field, null);
}
public Object getStaticScalar(String table, String id, String field, String format) {
// checks the Retired flag
BigDecimal stamp = this.getStaticScalarStamp(table, id, field);
if (stamp == null)
return null;
try {
Object val = this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, stamp, "Data");
// TODO format
return val;
}
catch (Exception x) {
OperationContext.get().error("getStaticScalar error: " + x);
}
return null;
}
public byte[] getStaticScalarRaw(String table, String id, String field) {
// checks the Retired flag
BigDecimal stamp = this.getStaticScalarStamp(table, id, field);
if (stamp == null)
return null;
try {
return this.conn.getRaw(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, stamp, "Data");
}
catch (Exception x) {
OperationContext.get().error("getStaticScalar error: " + x);
}
return null;
}
public RecordStruct getStaticScalarExtended(String table, String id, String field, String format) {
BigDecimal stamp = this.getStaticScalarStamp(table, id, field);
if (stamp == null)
return null;
try {
RecordStruct ret = new RecordStruct();
// TODO format data
ret.setField("Data", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, stamp, "Data"));
ret.setField("Tags", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, stamp, "Tags"));
ret.setField("Retired", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, stamp, "Retired"));
return ret;
}
catch (Exception x) {
OperationContext.get().error("getStaticScalar error: " + x);
}
return null;
}
public BigDecimal getStaticScalarStamp(String table, String id, String field) {
try {
byte[] olderStamp = this.conn.getOrNextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, this.task.getStamp());
if (olderStamp == null)
return null;
BigDecimal oldStamp = Struct.objectToDecimal(ByteUtil.extractValue(olderStamp));
if (this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, oldStamp, "Retired"))
return null;
return oldStamp;
}
catch (Exception x) {
OperationContext.get().error("getStaticScalar error: " + x);
}
return null;
}
public Object getStaticList(String table, String id, String field, String subid) {
return this.getStaticList(table, id, field, subid, null);
}
public Object getStaticList(String table, String id, String field, String subid, String format) {
// checks the Retired flag
BigDecimal stamp = this.getListStamp(table, id, field, subid, null);
if (stamp == null)
return null;
try {
Object val = this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data");
// TODO format
return val;
}
catch (Exception x) {
OperationContext.get().error("getStaticList error: " + x);
}
return null;
}
public byte[] getStaticListRaw(String table, String id, String field, String subid) {
// checks the Retired flag
BigDecimal stamp = this.getListStamp(table, id, field, subid, null);
if (stamp == null)
return null;
try {
return this.conn.getRaw(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data");
}
catch (Exception x) {
OperationContext.get().error("getStaticList error: " + x);
}
return null;
}
public RecordStruct getStaticListExtended(String table, String id, String field, String subid, String format) {
BigDecimal stamp = this.getListStamp(table, id, field, subid, null);
if (stamp == null)
return null;
try {
RecordStruct ret = new RecordStruct();
// TODO format
ret.setField("SubId", subid);
ret.setField("Data", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data"));
ret.setField("Tags", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Tags"));
ret.setField("Retired", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Retired"));
return ret;
}
catch (Exception x) {
OperationContext.get().error("getStaticList error: " + x);
}
return null;
}
public List<String> getStaticListKeys(String table, String id, String field) {
List<String> ret = new ArrayList<>();
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
ret.add(Struct.objectToString(sid));
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getStaticList error: " + x);
}
return ret;
}
public Object getDynamicScalar(String table, String id, String field, BigDateTime when) {
return this.getDynamicScalar(table, id, field, when, null, false);
}
public Object getDynamicScalar(String table, String id, String field, BigDateTime when, String format, boolean historical) {
String subid = this.getDynamicScalarSubId(table, id, field, when, historical);
if (StringUtil.isEmpty(subid))
return null;
// checks the Retired flag
BigDecimal stamp = this.getListStamp(table, id, field, subid, when);
if (stamp == null)
return null;
try {
Object val = this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data");
// TODO format
return val;
}
catch (Exception x) {
OperationContext.get().error("getDynamicScalar error: " + x);
}
return null;
}
public byte[] getDynamicScalarRaw(String table, String id, String field, BigDateTime when, boolean historical) {
String subid = this.getDynamicScalarSubId(table, id, field, when, historical);
if (StringUtil.isEmpty(subid))
return null;
// checks the Retired flag
BigDecimal stamp = this.getListStamp(table, id, field, subid, when);
if (stamp == null)
return null;
try {
return this.conn.getRaw(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data");
}
catch (Exception x) {
OperationContext.get().error("getDynamicScalar error: " + x);
}
return null;
}
public RecordStruct getDynamicScalarExtended(String table, String id, String field, BigDateTime when, String format, boolean historical) {
String subid = this.getDynamicScalarSubId(table, id, field, when, historical);
if (StringUtil.isEmpty(subid))
return null;
BigDecimal stamp = this.getListStamp(table, id, field, subid, when);
if (stamp == null)
return null;
try {
RecordStruct ret = new RecordStruct();
ret.setField("SubId", subid);
ret.setField("Data", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data"));
ret.setField("Tags", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Tags"));
ret.setField("Retired", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Retired"));
ret.setField("From", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "From"));
return ret;
}
catch (Exception x) {
OperationContext.get().error("getDynamicScalar error: " + x);
}
return null;
}
public String getDynamicScalarSubId(String table, String id, String field, BigDateTime when, boolean historical) {
if (when == null)
when = new BigDateTime();
if (!historical) {
BigDateTime to = Struct.objectToBigDateTime(this.getStaticScalar(table, id, "To"));
// when must come before to
if ((to != null) && (when.compareTo(to) != -1))
return null;
}
BigDateTime matchWhen = null;
String matchSid = null;
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
byte[] stmp = this.conn.getOrNextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid, this.task.getStamp());
if (stmp != null) {
Object stamp = ByteUtil.extractValue(stmp);
if (!this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid, stamp, "Retired")) {
BigDateTime from = this.conn.getAsBigDateTime(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid, stamp, "From");
if (from == null)
from = Struct.objectToBigDateTime(this.getStaticScalar(table, id, "From"));
if ((from == null) && (matchWhen == null))
matchSid = Struct.objectToString(sid);
// if `from` is before or at `when` and if `from` is greater than a previous match
else if ((from != null) && (from.compareTo(when) <= 0)) {
if ((matchWhen == null) || (from.compareTo(matchWhen) > 0)) {
matchWhen = from;
matchSid = Struct.objectToString(sid);
}
}
}
}
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicScalar error: " + x);
}
return matchSid;
}
public List<String> getDynamicScalarKeys(String table, String id, String field) {
List<String> ret = new ArrayList<>();
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
ret.add(Struct.objectToString(sid));
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicScalar error: " + x);
}
return ret;
}
public Object getDynamicList(String table, String id, String field, String subid, BigDateTime when) {
return this.getDynamicList(table, id, field, subid, when, null);
}
public Object getDynamicList(String table, String id, String field, String subid, BigDateTime when, String format) {
// checks the Retired flag
BigDecimal stamp = this.getListStamp(table, id, field, subid, when);
if (stamp == null)
return null;
try {
Object val = this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data");
// TODO format
return val;
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return null;
}
public byte[] getDynamicListRaw(String table, String id, String field, String subid, BigDateTime when) {
// checks the Retired flag
BigDecimal stamp = this.getListStamp(table, id, field, subid, when);
if (stamp == null)
return null;
try {
return this.conn.getRaw(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data");
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return null;
}
public RecordStruct getDynamicListExtended(String table, String id, String field, String subid, BigDateTime when, String format) {
BigDecimal stamp = this.getListStamp(table, id, field, subid, when);
if (stamp == null)
return null;
try {
RecordStruct ret = new RecordStruct();
ret.setField("SubId", subid);
ret.setField("Data", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Data"));
ret.setField("Tags", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Tags"));
ret.setField("Retired", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "Retired"));
ret.setField("From", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "From"));
ret.setField("To", this.conn.get(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, stamp, "To"));
return ret;
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return null;
}
public BigDecimal getListStamp(String table, String id, String field, String subid, BigDateTime when) {
try {
byte[] olderStamp = this.conn.getOrNextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, this.task.getStamp());
if (olderStamp == null)
return null;
BigDecimal oldStamp = Struct.objectToDecimal(ByteUtil.extractValue(olderStamp));
if (this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, oldStamp, "Retired"))
return null;
if (when == null)
return oldStamp;
BigDateTime to = this.conn.getAsBigDateTime(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, oldStamp, "To");
if (to == null)
to = Struct.objectToBigDateTime(this.getStaticScalar(table, id, "To"));
// if `to` is before or at `when` then bad
if ((to != null) && (to.compareTo(when) <= 0))
return null;
BigDateTime from = this.conn.getAsBigDateTime(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, subid, oldStamp, "From");
if (from == null)
from = Struct.objectToBigDateTime(this.getStaticScalar(table, id, "From"));
if (from == null)
return oldStamp;
// if `from` is before or at `when` then good
if (from.compareTo(when) <= 0)
return oldStamp;
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return null;
}
public List<String> getDynamicListKeys(String table, String id, String field) {
List<String> ret = new ArrayList<>();
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
ret.add(Struct.objectToString(sid));
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return ret;
}
public Object get(String table, String id, String field, BigDateTime when) {
return this.get(table, id, field, when, null, false);
}
public List<Object> get(String table, String id, String field, BigDateTime when, String format, boolean historical) {
List<Object> ret = new ArrayList<>();
DbField schema = this.task.getSchema().getDbField(table, field);
if (schema == null)
return ret;
if (!schema.isList() && !schema.isDynamic()) {
ret.add(this.getStaticScalar(table, id, field, format));
return ret;
}
if (!schema.isList() && schema.isDynamic()) {
ret.add(this.getDynamicScalar(table, id, field, when, format, historical));
return ret;
}
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
if (schema.isList() && !schema.isDynamic())
ret.add(this.getStaticList(table, id, field, Struct.objectToString(sid), format));
else
ret.add(this.getDynamicList(table, id, field, Struct.objectToString(sid), when, format));
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return ret;
}
public List<Object> getExtended(String table, String id, String field, BigDateTime when, String format, boolean historical) {
List<Object> ret = new ArrayList<>();
DbField schema = this.task.getSchema().getDbField(table, field);
if (schema == null)
return ret;
if (!schema.isList() && !schema.isDynamic()) {
ret.add(this.getStaticScalarExtended(table, id, field, format));
return ret;
}
if (!schema.isList() && schema.isDynamic()) {
ret.add(this.getDynamicScalarExtended(table, id, field, when, format, historical));
return ret;
}
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
if (schema.isList() && !schema.isDynamic())
ret.add(this.getStaticListExtended(table, id, field, Struct.objectToString(sid), format));
else
ret.add(this.getDynamicListExtended(table, id, field, Struct.objectToString(sid), when, format));
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
return ret;
}
// subid null for all
public List<byte[]> getRaw(String table, String id, String field, String subid, BigDateTime when, boolean historical) {
List<byte[]> ret = new ArrayList<>();
DbField schema = this.task.getSchema().getDbField(table, field);
if (schema == null)
return ret;
if (!schema.isList() && !schema.isDynamic()) {
ret.add(this.getStaticScalarRaw(table, id, field));
return ret;
}
if (!schema.isList() && schema.isDynamic()) {
ret.add(this.getDynamicScalarRaw(table, id, field, when, historical));
return ret;
}
if (subid != null) {
if (schema.isList() && !schema.isDynamic())
ret.add(this.getStaticListRaw(table, id, field, subid));
else
ret.add(this.getDynamicListRaw(table, id, field, subid, when)); // TODO check if this returns null sometimes, not what we want right?
}
else {
try {
byte[] bsubid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, null);
while (bsubid != null) {
Object sid = ByteUtil.extractValue(bsubid);
if (schema.isList() && !schema.isDynamic())
ret.add(this.getStaticListRaw(table, id, field, Struct.objectToString(sid)));
else
ret.add(this.getDynamicListRaw(table, id, field, Struct.objectToString(sid), when)); // TODO check if this returns null sometimes, not what we want right?
bsubid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, this.task.getDomain(), table, id, field, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
}
return ret;
}
/* TODO something like this...
; check to see if value is current with the given when+historical settings
has(value,table,id,field,when,format,historical) i (table="")!(id="")!(field="") quit 0
i (table'["#")&(Domain'="") s table=table_"#"_Domain ; support table instances
;
n fschema m fschema=^dcSchema($p(table,"#"),"Fields",field)
i 'fschema("List")&'fschema("Dynamic") quit ($$get1(table,id,field,format)=value)
i 'fschema("List")&fschema("Dynamic") quit ($$get3(table,id,field,when,format,historical)=value)
;
n sid,fnd
f s sid=$o(^dcRecord(table,id,field,sid)) q:sid="" d q:fnd
. s:($$get4(table,id,field,sid,when,format)=value) fnd=1
;
quit fnd
;
*/
public Object formatField(String table, String fname, Object value, String format) {
if ("Tr".equals(format)) {
// TODO translate $$tr^dcStrUtil("_enum_"_table_"_"_field_"_"_val)
}
// TODO format date/time to chrono
// TODO format numbers to locale
// TODO split? pad? custom format function?
return value;
}
public boolean checkSelect(String table, String id, BigDateTime when, RecordStruct where, boolean historical) {
if (!this.isCurrent(table, id, when, historical))
return false;
if (where == null)
return true;
boolean ret = false;
String expression = where.getFieldAsString("Expression"); // expression name - "And", "Or", "Equal", etc.
if (StringUtil.isEmpty(expression))
return ret;
/*
;
; Complex: And | Or | Not
; Simple: Equal | NotEqual | In | Any | Filter | Is | IsNot
; LessThan | GreaterThan | LessThanOrEqual | GreaterThanOrEqual | Between
; StartsWith | Contains
*/
if ("And".equals(expression)) {
ListStruct children = where.getFieldAsList("Children");
if (children != null) {
for (Struct s : children.getItems()) {
RecordStruct child = (RecordStruct) s;
ret = this.checkSelect(table, id, when, child, historical);
if (!ret)
break;
}
}
return ret;
}
if ("Or".equals(expression)) {
ListStruct children = where.getFieldAsList("Children");
if (children != null) {
for (Struct s : children.getItems()) {
RecordStruct child = (RecordStruct) s;
ret = this.checkSelect(table, id, when, child, historical);
if (ret)
break;
}
}
return ret;
}
// Not is only allowed 1 child
if ("Not".equals(expression)) {
ListStruct children = where.getFieldAsList("Children");
if (children != null) {
RecordStruct child = children.getItemAsRecord(0);
ret = !this.checkSelect(table, id, when, child, historical);
}
return ret;
}
if ("Filter".equals(expression)) {
/*
. s filter=where("Filter")
. i (filter'="")&(^dcProg("wherefilter",filter)'="") x "s res=$$"_^dcProg("wherefilter",filter)_"()"
*/
return ret;
}
String[] parts = new String[] { "A", "B", "C" };
List<List<byte[]>> values = new ArrayList<List<byte[]>>();
for (int i = 0; i < parts.length; i++) {
values.add(null); // start with null
RecordStruct pdef = where.getFieldAsRecord(parts[i]);
if (pdef == null)
continue;
String pfname = pdef.getFieldAsString("Field");
String subid = pdef.getFieldAsString("SubId");
// TODO String pformat = pdef.getFieldAsString("Format");
// add support for Format, this converts from byte to object, then formats object, then back to byte for compares
if (StringUtil.isNotEmpty(pfname)) {
if ("Id".equals(pfname)) {
ArrayList<byte[]> vl = new ArrayList<>();
vl.add(ByteUtil.buildValue(id));
values.set(i, vl);
}
else
values.set(i, this.getRaw(table, id, pfname, subid, when, historical));
continue;
}
String comp = pdef.getFieldAsString("Composer");
if (StringUtil.isNotEmpty(comp)) {
values.set(i, null); // TODO composer
continue;
}
Object val = pdef.getField("Value");
if (val != null) {
ArrayList<byte[]> vl = new ArrayList<>();
if (val instanceof ListStruct) {
for (int i2 = 0; i2 < ((ListStruct)val).getSize(); i2++)
vl.add(ByteUtil.buildValue(((ListStruct)val).getItem(i2)));
}
else {
vl.add(ByteUtil.buildValue(val));
}
values.set(i, vl);
}
}
List<byte[]> a = values.get(0);
List<byte[]> b = values.get(1);
List<byte[]> c = values.get(2);
if ("Equal".equals(expression)) {
if ((a == null) && (b == null))
return true;
// rule out one being null
if ((a == null) || (b == null))
return false;
for (int i = 0; i < Math.max(a.size(), b.size()); i++) {
if (i >= a.size())
return false;
if (i >= b.size())
return false;
byte[] a1 = a.get(i);
byte[] b1 = b.get(i);
if ((a1 == null) && (b1 == null))
return true;
// rule out one being null
if ((a1 == null) || (b1 == null))
return false;
if (ByteUtil.compareKeys(a1, b1) != 0)
return false;
}
return true;
}
if ("NotEqual".equals(expression)) {
if ((a == null) && (b == null))
return false;
if ((a == null) || (b == null))
return true;
for (int i = 0; i < Math.max(a.size(), b.size()); i++) {
if (i >= a.size())
return true;
if (i >= b.size())
return true;
if (ByteUtil.compareKeys(a.get(i), b.get(i)) == 0)
return false;
}
return true;
}
if ("Is".equals(expression)) {
if (a == null)
return false;
// TODO enhance for more than null?
for (int i = 0; i < a.size(); i++) {
if (ByteUtil.compareKeys(a.get(i), Constants.DB_EMPTY_ARRAY) == 0)
return false;
}
return true;
}
if ("IsNot".equals(expression)) {
if (a == null)
return true;
// TODO enhance for more than null?
for (int i = 0; i < a.size(); i++) {
if (ByteUtil.compareKeys(a.get(i), Constants.DB_EMPTY_ARRAY) == 0)
return true;
}
return false;
}
if ("LessThan".equals(expression)) {
if ((a == null) && (b == null))
return false;
if (a == null)
return true;
if (b == null)
return false;
int max = Math.max(a.size(), b.size());
for (int i = 0; i < max; i++) {
if (i >= a.size())
return true;
if (i >= b.size())
return false;
int comp = ByteUtil.compareKeys(a.get(i), b.get(i));
if ((comp == 0) && (i < max -1))
continue;
// first compare that is smaller wins
if (comp < 0)
return true;
// first compare that is larger loses
if (comp > 0)
return false;
}
// must be equal here
return false;
}
if ("GreaterThan".equals(expression)) {
if ((a == null) && (b == null))
return false;
if (a == null)
return false;
if (b == null)
return true;
int max = Math.max(a.size(), b.size());
for (int i = 0; i < max; i++) {
if (i >= a.size())
return false;
if (i >= b.size())
return true;
int comp = ByteUtil.compareKeys(a.get(i), b.get(i));
// first compare that is smaller loses
if (comp < 0)
return false;
// first compare that is larger wins
if (comp > 0)
return true;
}
// we must be equal here
return false;
}
if ("LessThanOrEqual".equals(expression)) {
if ((a == null) && (b == null))
return true;
if (a == null)
return true;
if (b == null)
return false;
int max = Math.max(a.size(), b.size());
for (int i = 0; i < max; i++) {
if (i >= a.size())
return true;
if (i >= b.size())
return false;
int comp = ByteUtil.compareKeys(a.get(i), b.get(i));
// first compare that is smaller wins
if (comp < 0)
return true;
// first compare that is larger loses
if (comp > 0)
return false;
}
return true;
}
if ("GreaterThanOrEqual".equals(expression)) {
if ((a == null) && (b == null))
return true;
if (a == null)
return false;
if (b == null)
return true;
int max = Math.max(a.size(), b.size());
for (int i = 0; i < max; i++) {
if (i >= a.size())
return false;
if (i >= b.size())
return true;
int comp = ByteUtil.compareKeys(a.get(i), b.get(i));
// first compare that is smaller loses
if (comp < 0)
return false;
// first compare that is larger wins
if (comp > 0)
return true;
}
return true;
}
if ("Between".equals(expression)) {
if ((a == null) && (b == null))
return true;
if (a == null)
return false;
if (b != null) {
// check the greater than or equal condition first
int max = Math.max(a.size(), b.size());
for (int i = 0; i < max; i++) {
if (i >= a.size())
return false;
if (i >= b.size())
break;
int comp = ByteUtil.compareKeys(a.get(i), b.get(i));
// first compare that is smaller loses
if (comp < 0)
return false;
// first compare that is larger wins
if (comp > 0)
break;
}
}
// now check the less than condition
if (c == null)
return false;
int max = Math.max(a.size(), c.size());
for (int i = 0; i < max; i++) {
if (i >= a.size())
return true;
if (i >= c.size())
return false;
int comp = ByteUtil.compareKeys(a.get(i), c.get(i));
// first compare that is smaller wins
if (comp < 0)
return true;
// first compare that is larger loses
if (comp > 0)
return false;
}
return true;
}
if ("Any".equals(expression)) {
if ((a == null) && (b == null))
return true;
// rule out one being null
if ((a == null) || (b == null))
return false;
for (int i = 0; i < a.size(); i++) {
for (int i2 = 0; i2 < b.size(); i2++) {
if (ByteUtil.compareKeys(a.get(i), b.get(i2)) == 0)
return true;
}
}
return false;
}
/* reconsider TODO
if ("In".equals(expression)) {
/*
i name="In" d quit res
. f s a=$o(aa(a)) q:a="" d q:res
. . s x="|"_aa(a)_"|"
. . i bb(0)[x s res=1
* /
return ret;
}
*/
if ("StartsWith".equals(expression)) {
// b is only allowed one value here
if ((a == null) || (b == null) || (b.size() != 1))
return false;
Object bv = ByteUtil.extractValue(b.get(0));
if (bv == null)
return false;
String bs = bv.toString();
for (int i = 0; i < a.size(); i++) {
Object av = ByteUtil.extractValue(a.get(i));
if (av == null)
return false;
if (!av.toString().startsWith(bs))
return false;
}
return true;
}
if ("EndsWith".equals(expression)) {
// b is only allowed one value here
if ((a == null) || (b == null) || (b.size() != 1))
return false;
Object bv = ByteUtil.extractValue(b.get(0));
if (bv == null)
return false;
String bs = bv.toString();
for (int i = 0; i < a.size(); i++) {
Object av = ByteUtil.extractValue(a.get(i));
if (av == null)
return false;
if (!av.toString().endsWith(bs))
return false;
}
return true;
}
if ("Contains".equals(expression)) {
// b is only allowed one value here
if ((a == null) || (b == null) || (b.size() != 1))
return false;
Object bv = ByteUtil.extractValue(b.get(0));
if (bv == null)
return false;
String bs = bv.toString();
for (int i = 0; i < a.size(); i++) {
Object av = ByteUtil.extractValue(a.get(i));
if (av == null)
return false;
if (!av.toString().contains(bs))
return false;
}
return true;
}
return ret;
}
public void traverseSubIds(String table, String id, String fname, BigDateTime when, boolean historical, Consumer<Object> out) {
String did = this.task.getDomain();
try {
byte[] subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, fname, null);
while (subid != null) {
Object sid = ByteUtil.extractValue(subid);
// if stamp is null it means Retired
if (this.getListStamp(table, id, fname, sid.toString(), when) != null)
out.accept(sid);
subid = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, fname, sid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
}
public void traverseRecords(String table, BigDateTime when, boolean historical, Consumer<Object> out) {
String did = this.task.getDomain();
try {
byte[] id = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, null);
while (id != null) {
Object oid = ByteUtil.extractValue(id);
if (this.isCurrent(table, oid.toString(), when, historical))
out.accept(oid);
id = this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, oid);
}
}
catch (Exception x) {
OperationContext.get().error("getDynamicList error: " + x);
}
}
public void traverseIndex(String table, String fname, Object val, BigDateTime when, boolean historical, Consumer<Object> out) {
this.traverseIndex(table, fname, val, null, when, historical, out);
}
public void traverseIndex(String table, String fname, Object val, String subid, BigDateTime when, boolean historical, Consumer<Object> out) {
String did = this.task.getDomain();
DbField ffdef = this.task.getSchema().getDbField(table, fname);
if (ffdef == null)
return;
if (val instanceof String)
val = val.toString().trim().toLowerCase(Locale.ROOT);
try {
byte[] recid = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val, null);
while (recid != null) {
Object rid = ByteUtil.extractValue(recid);
if (this.isCurrent(table, rid.toString(), when, historical)) {
if (ffdef.isStaticScalar()) {
out.accept(rid);
}
else {
byte[] recsid = conn.nextPeerKey(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, null);
while (recsid != null) {
Object rsid = ByteUtil.extractValue(recsid);
if ((subid == null) || subid.equals(rsid)) {
String range = conn.getAsString(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, rsid);
if (StringUtil.isEmpty(range) || (when == null)) {
out.accept(rid);
}
else {
int pos = range.indexOf(':');
BigDateTime from = null;
BigDateTime to = null;
if (pos == -1) {
from = BigDateTime.parseOrNull(range);
}
else if (pos == 0) {
to = BigDateTime.parseOrNull(range.substring(1));
}
else {
from = BigDateTime.parseOrNull(range.substring(0, pos));
to = BigDateTime.parseOrNull(range.substring(pos + 1));
}
if (((from == null) || (when.compareTo(from) >= 0)) && ((to == null) || (when.compareTo(to) < 0)))
out.accept(rid);
}
}
recsid = conn.nextPeerKey(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, rsid);
}
}
}
recid = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val, rid);
}
}
catch (Exception x) {
OperationContext.get().error("traverseIndex error: " + x);
}
}
public Object firstInIndex(String table, String fname, Object val, BigDateTime when, boolean historical) {
String did = this.task.getDomain();
DbField ffdef = this.task.getSchema().getDbField(table, fname);
if (ffdef == null)
return null;
if (val instanceof String)
val = val.toString().trim().toLowerCase(Locale.ROOT);
try {
byte[] recid = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val, null);
while (recid != null) {
Object rid = ByteUtil.extractValue(recid);
if (this.isCurrent(table, rid.toString(), when, historical)) {
if (ffdef.isStaticScalar())
return rid;
byte[] recsid = conn.nextPeerKey(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, null);
while (recsid != null) {
Object rsid = ByteUtil.extractValue(recsid);
String range = conn.getAsString(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, rsid);
if (StringUtil.isEmpty(range) || (when == null))
return rid;
int pos = range.indexOf(':');
BigDateTime from = null;
BigDateTime to = null;
if (pos == -1) {
from = BigDateTime.parseOrNull(range);
}
else if (pos == 0) {
to = BigDateTime.parseOrNull(range.substring(1));
}
else {
from = BigDateTime.parseOrNull(range.substring(0, pos));
to = BigDateTime.parseOrNull(range.substring(pos + 1));
}
if (((from == null) || (when.compareTo(from) >= 0)) && ((to == null) || (when.compareTo(to) < 0)))
return rid;
recsid = conn.nextPeerKey(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rsid);
}
}
recid = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val, rid);
}
}
catch (Exception x) {
OperationContext.get().error("traverseIndex error: " + x);
}
return null;
}
// traverse the values
public void traverseIndexValRange(String table, String fname, Object fromval, Object toval, BigDateTime when, boolean historical, Consumer<Object> out) {
String did = this.task.getDomain();
DbField ffdef = this.task.getSchema().getDbField(table, fname);
if (ffdef == null)
return;
if (fromval instanceof String)
fromval = fromval.toString().trim().toLowerCase(Locale.ROOT);
if (toval instanceof String)
toval = toval.toString().trim().toLowerCase(Locale.ROOT);
try {
byte[] valb = conn.getOrNextPeerKey(ffdef.getIndexName(), did, table, fname, fromval);
byte[] valfin = (toval != null) ? ByteUtil.buildKey(toval) : null;
while (valb != null) {
// check if past "To"
if ((valfin != null) && (ByteUtil.compareKeys(valb, valfin) >= 0))
break;
Object val = ByteUtil.extractValue(valb);
out.accept(val);
valb = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val);
}
}
catch (Exception x) {
OperationContext.get().error("traverseIndex error: " + x);
}
}
// traverse the record ids
public void traverseIndexRange(String table, String fname, Object fromval, Object toval, BigDateTime when, boolean historical, Consumer<Object> out) {
String did = this.task.getDomain();
DbField ffdef = this.task.getSchema().getDbField(table, fname);
if (ffdef == null)
return;
if (fromval instanceof String)
fromval = fromval.toString().trim().toLowerCase(Locale.ROOT);
if (toval instanceof String)
toval = toval.toString().trim().toLowerCase(Locale.ROOT);
try {
byte[] valb = conn.getOrNextPeerKey(ffdef.getIndexName(), did, table, fname, fromval);
byte[] valfin = (toval != null) ? ByteUtil.buildKey(toval) : null;
while (valb != null) {
// check if past "To"
if ((valfin != null) && (ByteUtil.compareKeys(valb, valfin) >= 0))
break;
Object val = ByteUtil.extractValue(valb);
byte[] recid = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val, null);
while (recid != null) {
Object rid = ByteUtil.extractValue(recid);
if (this.isCurrent(table, rid.toString(), when, historical)) {
if (ffdef.isStaticScalar()) {
out.accept(rid);
}
else {
byte[] recsid = conn.nextPeerKey(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, null);
while (recsid != null) {
Object rsid = ByteUtil.extractValue(recsid);
String range = conn.getAsString(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rid, rsid);
if (StringUtil.isEmpty(range) || (when == null)) {
out.accept(rid);
}
else {
int pos = range.indexOf(':');
BigDateTime from = null;
BigDateTime to = null;
if (pos == -1) {
from = BigDateTime.parseOrNull(range);
}
else if (pos == 0) {
to = BigDateTime.parseOrNull(range.substring(1));
}
else {
from = BigDateTime.parseOrNull(range.substring(0, pos));
to = BigDateTime.parseOrNull(range.substring(pos + 1));
}
if (((from == null) || (when.compareTo(from) >= 0)) && ((to == null) || (when.compareTo(to) < 0)))
out.accept(rid);
}
recsid = conn.nextPeerKey(DB_GLOBAL_INDEX_SUB, did, table, fname, val, rsid);
}
}
}
recid = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val, rid);
}
valb = conn.nextPeerKey(ffdef.getIndexName(), did, table, fname, val);
}
}
catch (Exception x) {
OperationContext.get().error("traverseIndex error: " + x);
}
}
public OperationResult executeTrigger(String table, String op, DatabaseInterface conn, DatabaseTask task, OperationResult log) {
OperationResult or = new OperationResult();
List<DbTrigger> trigs = this.task.getSchema().getDbTriggers(table, op);
for (DbTrigger trig : trigs) {
String spname = trig.execute;
try {
Class<?> spclass = Class.forName(spname);
IStoredProc sp = (IStoredProc) spclass.newInstance();
sp.execute(conn, task, log);
}
catch (Exception x) {
or.error("Unable to load/start tigger class: " + x);
}
}
return or;
}
/*
; TODO improve so source can be table or script
; Params("Sources",[table name],"Title")=[field name]
; Params("Sources",[table name],"Body")=[field name]
; Params("Sources",[table name],"Extras",[field name])=1
;
TODO wrap up to above sids are part of a source
; Params("AllowedSids",[table name],[field name],[sid])=1 - if field name not present then assume all
;
; Params("RequiredWords",[word],"Term")=1
; ,"Exact")=[exact word for final match in Original]
;
; Params("AllowedWords",[word],"Term")=1
; ,"Exact")=[exact word for final match in Original]
;
; Params("ProhibitedWords",[word],"Term")=1
; ,"Exact")=[exact word for final match in Original]
;
; returns top results first, returns only 1000
; and no more than 100 or so per field...that is all one should ever need
;
; RETURN = [
; {
; Table: N,
; Id: N,
; Score: N,
; TitlePositions: [ N, n ], // relative to Title, where 0 is first character
; Title: SSSS
; BodyPositions: [ N, n ], // relative to Body, where 0 is first character
; Body: SSSS
; }
; ]
;
srchTxt n params2,table,field,ret,match,score,id,ttitle,tbody,tab,sid,word,plist,i
;
; srchTxt2 is the heart of the searching, this routine just formats those results
; so we need to prep some parameters for srchTxt2
;
m params2("RequiredWords")=Params("RequiredWords")
m params2("AllowedWords")=Params("AllowedWords")
m params2("ProhibitedWords")=Params("ProhibitedWords")
m params2("AllowedSids")=Params("AllowedSids")
;
; convert sources to the srchTxt2 structure
;
f s table=$o(Params("Sources",table)) q:table="" d
. s ttitle=Params("Sources",table,"Title")
. s:ttitle'="" params2("Sources",table,ttitle)=1
. ;
. s tbody=Params("Sources",table,"Body")
. s:tbody'="" params2("Sources",table,tbody)=1
. ;
. k field f s field=$o(Params("Sources",table,"Extras",field)) q:field="" d
. . s params2("Sources",table,field)=1
;
; collect search results
;
d srchTxt2(.params2,.ret)
;
; return the results
;
w StartList
;
f s score=$o(ret(score),-1) q:score="" d
. f s table=$o(ret(score,table)) q:table="" d
. . s tab=$p(table,"#",1)
. . f s id=$o(ret(score,table,id)) q:id="" d
. . . w StartRec
. . . w Field_"Table"_ScalarStr_tab
. . . w Field_"Id"_ScalarStr_id
. . . w Field_"Score"_ScalarInt_score
. . . ;
. . . s ttitle=Params("Sources",tab,"Title")
. . . s tbody=Params("Sources",tab,"Body")
. . . ;
. . . w Field_"Title"_ScalarStr
. . . s sid=$o(^dcTextRecord(table,id,ttitle,""))
. . . ;
. . . i sid'="" d
. . . . w ^dcTextRecord(table,id,ttitle,sid,"Original",0) ; titles are no more than one line
. . . . ;
. . . . i $d(ret(score,table,id,ttitle,sid))>0 d
. . . . . w Field_"TitlePositions"_StartList
. . . . . ;
. . . . . f s word=$o(ret(score,table,id,ttitle,sid,word)) q:word="" d
. . . . . . s plist=ret(score,table,id,ttitle,sid,word)
. . . . . . f i=1:1:$l(plist,",") w ScalarInt_$p(plist,",",i)
. . . . . ;
. . . . . w EndList
. . . . ;
. . . s tbody=Params("Sources",tab,"Body")
. . . ;
. . . w Field_"Body"_ScalarStr
. . . k sid f s sid=$o(^dcTextRecord(table,id,tbody,sid)) q:sid="" d
. . . . ;
. . . . ;i $d(ret(score,table,id,tbody,sid))>0 d q
. . . . ;. ; TODO find the positions and cut out parts
. . . . ;
. . . . ; if we get here then we are just writing the top 30 words
. . . . s sentence=^dcTextRecord(table,id,tbody,sid,"Original",0)
. . . . k wcnt f i=1:1:$l(sentence) q:wcnt=30 i $e(sentence,i)=" " s wcnt=wcnt+1
. . . . w $e(sentence,1,i-1)
. . . ;
. . . w EndRec
;
w EndList
;
quit
;
;
; Params("Sources",[table name],[field name])=1
;
; Params("AllowedSids",[table name],[field name],[sid])=1 - if field name not present then assume all
; TODO AllowedSids not yet coded!!
; Params("RequiredWords",[word],"Term")=1
; ,"Exact")=[exact word for final match in Original]
;
; Params("AllowedWords",[word],"Term")=1
; ,"Exact")=[exact word for final match in Original]
;
; Params("ProhibitedWords",[word],"Term")=1
; ,"Exact")=[exact word for final match in Original]
;
; ret
; ret(score,table,id,field,sid,word)=[comma list of positions]
;
srchTxt2(params,ret) n score,table,field,id,sid,pos,word,sources,find,fnd,sscore,tabled
n exact,lnum,matches,fmatch,ismatch,word2,entry,term,nxtscore,collect
;
; create one list of all words we are searching for
;
f s word=$o(params("RequiredWords",word)) q:word="" s find(word)=1,lnum=lnum+1
f s word=$o(params("AllowedWords",word)) q:word="" s find(word)=1,lnum=lnum+1
;
s lnum=$s(lnum>5:20,lnum>3:50,1:100) ; limit how many partial matches we look at if we have many words
;
; prime the sources array - we want the top scoring word for each table and field
; we'll then use this array to figure the top score of all. as we loop the sources
; we'll keep adding more top matches so we find the next top scoring
;
f s table=$o(params("Sources",table)) q:table="" d
. i (table'["#")&(Domain'="") s tabled=table_"#"_Domain ; support table instances
. e s tabled=table
. ;
. f s field=$o(params("Sources",table,field)) q:field="" d
. . f s word=$o(find(word)) q:word="" d
. . . s score=$o(^dcTextIndex(tabled,field,word,""),-1)
. . . i score'="" s sources(score,word,tabled,field)=1 ; sources will get filled out further down
. . . ;
. . . i (params("RequiredWords",word,"Exact")'="")!(params("AllowedWords",word,"Exact")'="") q
. . . k matches
. . . s word2=word
. . . f s word2=$o(^dcTextIndex(tabled,field,word2)) q:word2="" d q:matches>(lnum-1)
. . . . i $f(word2,word)'=($l(word)+1) s matches=lnum q ; if not starting with the original word then stop looking
. . . . s score=$o(^dcTextIndex(tabled,field,word2,""),-1)
. . . . i score'="" s sources(score,word2,tabled,field)=1,matches=matches+1
;
; find our top scoring fields/words and then use the text index to find possible
; record matches.
;
k score,matches
f s score=$o(sources(score),-1) q:score="" d q:matches>999
. f s word=$o(sources(score,word)) q:word="" d
. . f s table=$o(sources(score,word,table)) q:table="" d
. . . k field
. . . f s field=$o(sources(score,word,table,field)) q:field=""!(fmatch(table,field)>99) d
. . . . k id
. . . . f s id=$o(^dcTextIndex(table,field,word,score,id)) q:id=""!(fmatch(table,field)>99) d
. . . . . k sid
. . . . . f s sid=$o(^dcTextIndex(table,field,word,score,id,sid)) q:sid=""!(fmatch(table,field)>99) d
. . . . . . ; check exact matches - if a required or allowed word is to have an exact match
. . . . . . ;
. . . . . . k ismatch
. . . . . . ;
. . . . . . i params("RequiredWords",word) d i 1
. . . . . . . s exact=params("RequiredWords",word,"Exact") i exact="" s ismatch=1 q
. . . . . . . k lnum f s lnum=$o(^dcTextRecord(table,id,field,sid,"Original",lnum)) q:lnum="" d q:ismatch
. . . . . . . . i ^dcTextRecord(table,id,field,sid,"Original",lnum)[exact s ismatch=1
. . . . . . ;
. . . . . . e i params("AllowedWords",word) d i 1
. . . . . . . s exact=params("AllowedWords",word,"Exact") i exact="" s ismatch=1 q
. . . . . . . k lnum f s lnum=$o(^dcTextRecord(table,id,field,sid,"Original",lnum)) q:lnum="" d q:ismatch
. . . . . . . . i ^dcTextRecord(table,id,field,sid,"Original",lnum)[exact s ismatch=1
. . . . . . ;
. . . . . . e s ismatch=1
. . . . . . ;
. . . . . . q:'ismatch
. . . . . . ;
. . . . . . ; check prohibited - see if a prohibited word is in a match
. . . . . . ;
. . . . . . k word2 f s word2=$o(params("ProhibitedWords",word2)) q:word2="" d q:'ismatch
. . . . . . . s exact=params("ProhibitedWords",word2,"Exact")
. . . . . . . s entry=$s(exact="":"Analyzed",1:"Original"),term=$s(exact="":"|"_word2_":",1:word2)
. . . . . . . k lnum f s lnum=$o(^dcTextRecord(table,id,field,sid,entry,lnum)) q:lnum="" d q:'ismatch
. . . . . . . . i ^dcTextRecord(table,id,field,sid,term,lnum)[term s ismatch=0
. . . . . . ;
. . . . . . q:'ismatch
. . . . . . ;
. . . . . . s collect(table,id,field,sid,word)=score ; collect contains the values we need for ordering our results
. . . . . . ;
. . . . . . s fmatch(table,field)=fmatch(table,field)+1,matches=matches+1
. . . . ;
. . . . s nxtscore=$o(^dcTextIndex(table,field,word,score),-1) q:nxtscore=""
. . . . s sources(nxtscore,word,table,field)=1 ; filling out sources
;
; build return value - we now have enough words, just want to put them in the right order
;
k table,field,id,sid,word,matches
;
f s table=$o(collect(table)) q:table="" d q:matches>249
. f s id=$o(collect(table,id)) q:id="" d q:matches>249
. . s ismatch=1
. . ;
. . ; ensure all required are present - unlike prohibited, which we can check above,
. . ; we have to check required across all potential fields for a given record
. . ;
. . k word2 f s word2=$o(params("RequiredWords",word2)) q:word2="" d q:'ismatch
. . . q:word=word2 ; already checked
. . . s exact=params("RequiredWords",word2,"Exact"),fnd=0
. . . s entry=$s(exact="":"Analyzed",1:"Original"),term=$s(exact="":"|"_word2_":",1:word2)
. . . ;
. . . ; check all fields/sids
. . . ;
. . . k field f s field=$o(params("Sources",$p(table,"#",1),field)) q:field="" d q:fnd
. . . . k sid f s sid=$o(^dcTextRecord(table,id,field,sid)) q:sid="" d q:fnd
. . . . . k lnum f s lnum=$o(^dcTextRecord(table,id,field,sid,entry,lnum)) q:lnum="" d q:fnd
. . . . . . i ^dcTextRecord(table,id,field,sid,entry,lnum)[term s fnd=1
. . . ;
. . . s:'fnd ismatch=0
. . ;
. . q:'ismatch
. . ;
. . ; compute score for the record
. . s score=0
. . ;
. . k field f s field=$o(collect(table,id,field)) q:field="" d
. . . k sid f s sid=$o(collect(table,id,field,sid)) q:sid="" d
. . . . k word f s word=$o(collect(table,id,field,sid,word)) q:word="" d
. . . . . s sscore=collect(table,id,field,sid,word)
. . . . . ;
. . . . . ; bonus if the word we are scoring matches one of the original words
. . . . . i params("AllowedWords",word)!params("RequiredWords",word) d
. . . . . . s lnum=collect(table,id,field,sid,word)
. . . . . . s sscore=sscore+($l(^dcTextIndex(table,field,word,lnum,id,sid),",")*2) ; bonus for each word occurance
. . . . . ;
. . . . . s score=score+sscore
. . ;
. . ; we now have the score for the record
. . ;
. . k field f s field=$o(collect(table,id,field)) q:field="" d
. . . k sid f s sid=$o(collect(table,id,field,sid)) q:sid="" d
. . . . k word f s word=$o(collect(table,id,field,sid,word)) q:word="" d
. . . . . s lnum=collect(table,id,field,sid,word)
. . . . . s ret(score,table,id,field,sid,word)=^dcTextIndex(table,field,word,lnum,id,sid)
. . . ;
. . s matches=matches+1 ; record matches, not word matches
;
quit
;
;
*
*/
public void rebuildIndexes() {
this.rebuildIndexes(Hub.instance.getDomainInfo(this.task.getDomain()), BigDateTime.nowDateTime());
}
public void rebuildIndexes(DomainInfo di, BigDateTime when) {
try {
for (DbTable tbl : di.getSchema().getDbTables()) {
this.rebuildTableIndex(di, tbl.getName(), when);
}
/*
byte[] traw = this.conn.nextPeerKey(DB_GLOBAL_RECORD, di.getId(), null);
while (traw != null) {
Object table = ByteUtil.extractValue(traw);
this.rebuildTableIndex(di, table.toString(), when);
traw = this.conn.nextPeerKey(DB_GLOBAL_RECORD, di.getId(), table);
}
*/
}
catch (Exception x) {
OperationContext.get().error("rebuildDomainIndexes error: " + x);
}
finally {
task.popDomain();
}
}
public void rebuildTableIndex(String table) {
this.rebuildTableIndex(Hub.instance.getDomainInfo(this.task.getDomain()), table, BigDateTime.nowDateTime());
}
public void rebuildTableIndex(DomainInfo di, String table, BigDateTime when) {
try {
// kill the indexes
this.conn.kill(DB_GLOBAL_INDEX_SUB, di.getId(), table);
this.conn.kill(DB_GLOBAL_INDEX, di.getId(), table);
// see if there is even such a table in the schema
//DomainInfo di = this.dm.getDomainInfo(did);
if (!di.getSchema().hasTable(table)) {
System.out.println("Skipping table, not known by this domain: " + table);
}
else {
System.out.println("Indexing table: " + table);
this.traverseRecords(table, when, false, new Consumer<Object>() {
@Override
public void accept(Object id) {
for (DbField schema : di.getSchema().getDbFields(table)) {
if (!schema.isIndexed())
continue;
String did = di.getId();
try {
// --------------------------------------
// StaticScalar handling
// --------------------------------------
if (!schema.isList() && !schema.isDynamic()) {
// find the first, newest, stamp
byte[] nstamp = TablesAdapter.this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, schema.getName(), null);
if (nstamp == null)
continue;
BigDecimal stamp = Struct.objectToDecimal(ByteUtil.extractValue(nstamp));
if (stamp == null)
continue;
if (TablesAdapter.this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, did, table, id, schema.getName(), stamp, "Retired"))
continue;
if (!TablesAdapter.this.conn.isSet(DB_GLOBAL_RECORD, did, table, id, schema.getName(), stamp, "Data"))
continue;
Object value = TablesAdapter.this.conn.get(DB_GLOBAL_RECORD, did, table, id, schema.getName(), stamp, "Data");
if (value instanceof String)
value = value.toString().toLowerCase(Locale.ROOT);
// increment index count
// set the new index new
TablesAdapter.this.conn.inc(DB_GLOBAL_INDEX, did, table, schema.getName(), value);
TablesAdapter.this.conn.set(DB_GLOBAL_INDEX, did, table, schema.getName(), value, id, null);
}
else {
TablesAdapter.this.traverseSubIds(table, id.toString(), schema.getName(), when, false, new Consumer<Object>() {
@Override
public void accept(Object sid) {
try {
// find the first, newest, stamp
byte[] nstamp = TablesAdapter.this.conn.nextPeerKey(DB_GLOBAL_RECORD, did, table, id, schema.getName(), sid, null);
if (nstamp == null)
return;
BigDecimal stamp = Struct.objectToDecimal(ByteUtil.extractValue(nstamp));
if (stamp == null)
return;
if (TablesAdapter.this.conn.getAsBooleanOrFalse(DB_GLOBAL_RECORD, did, table, id, schema.getName(), sid, stamp, "Retired"))
return;
if (!TablesAdapter.this.conn.isSet(DB_GLOBAL_RECORD, did, table, id, schema.getName(), sid, stamp, "Data"))
return;
Object value = TablesAdapter.this.conn.get(DB_GLOBAL_RECORD, did, table, id, schema.getName(), sid, stamp, "Data");
Object from = TablesAdapter.this.conn.get(DB_GLOBAL_RECORD, did, table, id, schema.getName(), sid, stamp, "From");
Object to = TablesAdapter.this.conn.get(DB_GLOBAL_RECORD, did, table, id, schema.getName(), sid, stamp, "To");
if (value instanceof String)
value = value.toString().toLowerCase(Locale.ROOT);
String range = null;
if (from != null)
range = from.toString();
if (to != null) {
if (range == null)
range = ":" + to.toString();
else
range += ":" + to.toString();
}
// increment index count
// set the new index new
TablesAdapter.this.conn.inc(DB_GLOBAL_INDEX_SUB, did, table, schema.getName(), value);
TablesAdapter.this.conn.set(DB_GLOBAL_INDEX_SUB, did, table, schema.getName(), value, id, sid, range);
}
catch (Exception x) {
System.out.println("Error indexing table: " + table + " - " + schema.getName() + " - " + id + " - " + sid + ": " + x);
}
}
});
}
}
catch (Exception x) {
System.out.println("Error indexing table: " + table + " - " + schema.getName() + " - " + id + ": " + x);
}
}
}
});
}
}
catch (DatabaseException x) {
System.out.println("Error indexing table: " + table + ": " + x);
}
}
}