Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the idiomatic way to implement foreign keys in CouchDB?

Let me state a simple example: You have an Order and a Shopping Cart. One way I envision persisting this is to save an Order document and a Cart document. The Order document could have a field called "shopping-cart" whose value is the UUID of the relevant Cart document. Another way I can imagine doing this is to save an Order document with the "shopping-cart" field containing an associative array of the entire Cart. In other words, instead of saving the Cart explicitly as an independent document, I embed the Cart document in the Order document.

What if we decide later that a Cart should be persistent, so a returning user will find his half-finished Cart waiting for him across sessions? I imagine we could then combine both methods, keeping the Cart separate while it's incomplete and embedding it in the Order document when it's finalized/purchased.

Both methods would work, though I worry about CouchDB not having foreign key constraints; in the first method the Cart document could be deleted, leaving you with a corrupt data set.

How do you decide which method to use? Is one of these methods more idiomatic to CouchDB? Are there any methods I missed?

I'm new to CouchDB so it's difficult for me to see the advantages/disadvantages to having a more or less normalized structure.

like image 396
Robert Campbell Avatar asked Nov 04 '09 14:11

Robert Campbell


People also ask

How do you decide where to put foreign key?

When you join the two tables together, the primary key of the parent table will be set equal to the foreign key of the child table. Whichever one is not the primary key is the foreign key. In one-to-many relationships, the FK goes on the "many" side.

What is a foreign key and why might we want to use one?

The FOREIGN KEY constraint is used to prevent actions that would destroy links between tables. A FOREIGN KEY is a field (or collection of fields) in one table, that refers to the PRIMARY KEY in another table.


1 Answers

If on the other hand they aren't one to one then using a complex key you can use view collation to do a join.

function(doc) {
  if (doc.type == 'order') {
    emit([doc.cartid, 1], doc);
  } else if (doc.type == 'cart') {
    emit([doc.id, 0], doc);
  }
}

the docs will be collated by cartid with the orders coming after the cart. Your application code can easily join this stream and you can query by a particular cartid using startkey and endkey.

see: View Collation for the collation rules.

You can also use a reduce to join them together as well.

Just change the map function to this:

function(doc) {
  if (doc.type == 'order') {
    emit([doc.cartid, 1], {cartid: doc.cartid, orders: [doc]});
  } else if (doc.type == 'cart') {
    emit([doc.id, 0], {cartid: doc.id, orders: [], cart: doc);
  }
}

and add a reduce function like this:

function(keys, values) {
  var out = {cartid: null, orders: [], cart: null};
  for (idx in values) {
    var doc = values[idx];
    out['cartid'] = doc.cartid;
    if (doc.cart) { out['cart'] = doc.cart };
    for (idx2 in doc.orders) {
      out.orders.push(doc.orders[idx2]);
    }
  }
  return out;
}

This will return a single document per cart which will a cartid, an array of order documents and a cart document.

Apologies if there are errors in the above code but I don't have a couchdb test instance handy to try them out. You should get the general idea though and the CouchDB wiki has more details.

like image 54
Jeremy Wall Avatar answered Nov 09 '22 21:11

Jeremy Wall