CouchDB’s Mango query language can select documents based on arbitrary JSON fields, not just top-level ones.

Let’s see this in action. Suppose we have a products database with documents like this:

{
  "_id": "abc",
  "_rev": "1-abc",
  "name": "Wireless Mouse",
  "details": {
    "brand": "LogiTech",
    "color": "Black",
    "weight_grams": 100
  },
  "tags": ["computer", "accessory", "wireless"]
}

We want to find all black wireless mice. We can use a Mango query like this:

{
  "selector": {
    "details.color": "Black",
    "name": "Wireless Mouse"
  }
}

This query targets the color field nested within the details object. The name field is at the top level. CouchDB’s Mango engine handles these nested field traversals seamlessly.

The core problem Mango selectors solve is efficient, index-backed querying of document content without resorting to full scans or complex map-reduce functions for simple filtering. When you define an index on a field, CouchDB builds a data structure that allows it to quickly locate documents matching specific criteria. For nested fields, CouchDB essentially flattens the path during indexing and querying, making details.color as queryable as a top-level color field.

Here’s how you’d create an index to support this query:

// Using the _design/mydesign document
{
  "_id": "_design/mydesign",
  "language": "javascript",
  "indexes": {
    "products_by_color_and_name": {
      "analyzer": "standard",
      "index": "function (doc) { if (doc.details && doc.details.color && doc.name) { emit(doc.details.color, doc.name); } }"
    }
  }
}

This index, when queried with details.color and name, will be used. Note that while the index definition above uses a map function, Mango selectors operate on their own indexing mechanism. To enable Mango selectors for nested fields, you simply define a Mango index directly:

{
  "_id": "_design/mydesign",
  "language": "query",
  "indexes": {
    "products_by_details_color": {
      "analyzer": "standard",
      "index": {
        "fields": [
          {"details.color": "asc"}
        ]
      }
    },
    "products_by_name": {
      "analyzer": "standard",
      "index": {
        "fields": [
          {"name": "asc"}
        ]
      }
    }
  }
}

When you execute the Mango query {"selector": {"details.color": "Black", "name": "Wireless Mouse"}}, CouchDB will look for an index that can satisfy both conditions. If you have separate indexes on details.color and name, CouchDB can use both and intersect their results. If you have a composite index like {"fields": ["details.color", "name"]}, it will use that single index.

The selector syntax supports various operators. For example, to find products with a weight less than 150 grams:

{
  "selector": {
    "details.weight_grams": {"$lt": 150}
  }
}

Or to find products tagged with either "electronics" or "gadget":

{
  "selector": {
    "tags": {"$in": ["electronics", "gadget"]}
  }
}

This query uses the $in operator to check if any element in the tags array matches the provided list.

The most surprising thing about Mango selectors and nested fields is that you don’t explicitly tell CouchDB how to index nested fields in the same way you might with some relational databases. You simply reference the . notation in your selector, and if a suitable index exists or can be created (e.g., with {"fields": ["details.color"]}), CouchDB figures out how to traverse the document structure. It’s a more declarative approach to indexing nested data.

To query within arrays of objects, you’d use a structure like this if you had documents like:

{
  "_id": "def",
  "name": "Laptop",
  "components": [
    {"type": "cpu", "model": "i7"},
    {"type": "ram", "size_gb": 16}
  ]
}

To find laptops with 16GB RAM:

{
  "selector": {
    "components": {
      "$elemMatch": {
        "type": "ram",
        "size_gb": 16
      }
    }
  }
}

The $elemMatch operator is crucial here; it ensures that a single element in the components array matches all the specified conditions. Without it, a document with one component of type "ram" and another component with size 16GB (but not necessarily the same component) would incorrectly match.

The next logical step after mastering nested selectors is understanding how to combine them with aggregation operators in Mango for more complex data analysis.

Want structured learning?

Take the full Couchdb course →