Couchbase Lite 1.x - Update view when a non-emitted document is updated


#1

I’m using Couchbase Lite Android 1.4.1.

I wanted to have a View that emits values based on keys that come from multiple documents and I managed to do it by getting linked documents inside the emit function but then my index is updated only when the base document is updated and not when one of the linked documents is being updated.

So my base documents are structured like these (minimal version):

{
	"type": "EVENT",
	"configurableMetadataId": "",
	"isComplete": true,
	"visitId": "xxx",
        "patientId": ""
} 

And the linked document is:

{
	"type": "VISIT",
	"visitStatus": "OPEN",
        "patientId": ""
}

And here is my emit function:

public void emit(@NonNull Map<String, Object> properties, Emitter emitter) {
	if(!properties.get(FIELDS.TYPE).equals("EVENT"))
		return;
		
	final Database database = getDatabase();
	Document visitDoc = database.getDocument(((String) properties.get(FIELDS.VISIT_ID)));
	if(visitDoc == null)
		return;

	List<Object> keys = new ArrayList<>();
	keys.add(properties.get(FIELDS.CONFIGURABLE_METADATA_ID));
	keys.add(visitDoc.getProperties().get(FIELDS.VISIT_STATUS));
	keys.add(properties.get(FIELDS.IS_COMPLETE));
	emitter.emit(keys, properties.get(FIELDS.PATIENT_ID);

	emitter.emit(keys, patientId);
}

So I would like to update the View when the visitDoc is being updated (or at least when the visit status is being updated).

Any ideas?

Thanks.


#2

Your idea is not possible given the way that views work. There is a requirement that the map function is “pure” (i.e. does not rely on any external state, and that any given set of input properties will always produce the same result). Anything you’d like to emit needs to be directly included in the document that is being indexed so the properties in question need to be a part of the visit document itself and not in a separate one.


#3

Ok it make senses. But do you have any idea on how to implement the desire behavior? Those documents cannot be merged into a single one as it will break down an already existing architecture.

I could split the query into 2 views (one using the result of the other) but it doesn’t seems to be really efficient.


#4

The usual way to do this is to have both types of documents emit the same key (in this case the visitID), and the desired properties as the value. Then you can either query for all rows with the desired visitID and merge the values together, or you can use a reduce function that does the same thing.

(Also, it seems weird in your code that you’re putting all those values into the key. Did you intend to create a multi-level sort on (id, status, isComplete, patientID) or was that a mistake?)


#5

The keys are for sorting and filtering. But thanks for the idea of both documents emitting the same ID, I didn’t think of it.


#6

It’s an old classic CouchDB trick :slight_smile:
I gave an “Advanced Couchbase Lite” talk at Connect 2015(?) where I went into more detail & gave an example.


#7

I got the talk you’re talking about: https://youtu.be/mFQTGiCEHrE?t=1090
I’m gonna try it and see if I manage to make it working with my documents structure.


#8

Maybe I don’t fully understand the reduce function but it doesn’t seem to be what I’m searching for.
I want to be able to query something like this:

Return all patientId where there is a visit with status X and at least one of the event linked to the visit has isComplete to false and the configurableMetadataId = Y

So my keys are isComplete and configurableMetadataId that comes from event doc types plus status that comes from the visit document.

I tried to use pseudo-joins as stated inside the video but I’m struggling on how to use it with my use case as I don’t need to aggregate values but add a property in the returned value.

I updated my emit function:

String docType = (String) properties.get(FIELDS.TYPE);
String patientFullName = getPatientFullName(properties);
String key = ((String) properties.get(FIELDS.PATIENT_ID));
List<Object> values = new ArrayList<>(Arrays.asList(docType, patientFullName));
switch (docType) {
	case Config.CB_EVENT_DOC_TYPE:
		values.addAll(Arrays.asList(properties.get(FIELDS.CONFIGURABLE_METADATA_ID), properties.get(FIELDS.IS_COMPLETE)));
		break;
	case Config.CB_VISIT_DOC_TYPE:
		values.add(Collections.singletonList(properties.get(CouchbaseVisitManager.FIELDS.STATUS)));
		break;
}
if (key != null)
	emitter.emit(key, values);

And I added a reduce function but I don’t really know how to implement it:

if(rereduce)
	return values;
List<Object> visitValues = null;
for (Object input : values) {
	if (input instanceof List && ((List) input).get(0) == Config.CB_VISIT_DOC_TYPE) {
		visitValues = ((List) input);
		break;
	}
}
Visit.Status status = visitValues != null ? CouchbaseUtils.getEnumFromValueField(Visit.Status.class, visitValues.get(2)) : null;
return Arrays.asList(status, values);

The patientName is for ordering on patient names.
EDIT: I added the patientId in the example on the first post.


#9

Have you considered just storing the events as an array in the visit document? That would make your querying much easier. This isn’t a SQL database, you don’t have to normalize…


#10

Unfortunately we have some reasons to not change our data structure (it is already used in prod, it’s focused on offline first and try to avoid conflicts by keeping the atomicity of the data, etc.). I managed to fix it by combining 2 queries. I may update my answer once we get into couchbase lite 2.x (it seems the N1QL-like feature gives more flexibility on the way to get data out from local databases).

Tank you for your time.


#11

Yeah, this is pretty straightforward to do in 2.x using a join.