Skip to content
Open
2 changes: 2 additions & 0 deletions src/couch/src/couch_query_servers.erl
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ validate_doc_update(Db, DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
case Resp of
ok ->
ok;
{[{<<"forbidden">>, Message}, {<<"failures">>, Failures}]} ->
throw({forbidden, Message, Failures});
{[{<<"forbidden">>, Message}]} ->
throw({forbidden, Message});
{[{<<"unauthorized">>, Message}]} ->
Expand Down
72 changes: 71 additions & 1 deletion src/docs/src/ddocs/ddocs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,8 @@ To use Mango selectors for validation, the design document must have the
containing the following fields:

* ``newDoc``: New version of document that will be stored.
* ``oldDoc``: Previous version of document that is already stored.
* ``oldDoc``: Previous version of document that is already stored; this field is
absent if the doc is being created for the first time.

For example, to check that all docs contain a ``title`` which is a string, and a
``year`` which is a number:
Expand Down Expand Up @@ -1012,3 +1013,72 @@ this design document:
}
}
}

By using the ``oldDoc`` field, we can create rules that say a document can only
be updated if it is currently in a certain state. For example, this rule would
enforce that only documents describing actors can be updated:

.. code-block:: json

{
"language": "query",

"validate_doc_update": {
"oldDoc": { "type": "actor" }
}
}

This also makes it so that no new documents can be created, because a write is
only accepted if a previous version of the doc already exists. To relax this
constraint, allow ``oldDoc`` not to exist:

.. code-block:: json

{
"language": "query",

"validate_doc_update": {
"oldDoc": {
"$or": [
{ "$exists": false },
{ "type": "actor" }
]
}
}
}

This validator will allow any new document creation, and updates to docs where
the ``type`` field is ``"actor"``. We can also have multiple rules for new
document states that depend on the current state, by combining ``$or`` with
several sets of ``{ oldDoc, newDoc }`` rules:

.. code-block:: json

{
"language": "query",

"validate_doc_update": {
"$or": [
// allow creation of docs with an acceptable type
{
"oldDoc": { "$exists": false },
"newDoc": {
"type": { "$in": ["movie", "actor"] }
}
},
// if a doc currently has "type": "actor", make sure its "movies"
// field is a non-empty list of strings
{
"oldDoc": { "type": "actor" },
"newDoc": {
"movies": {
"$type": "array",
"$not": { "$size": 0 },
"$allMatch": { "$type": "string" }
}
}
},
// etc.
]
}
}
32 changes: 24 additions & 8 deletions src/mango/src/mango_native_proc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,30 @@ handle_call({prompt, [<<"ddoc">>, DDocId, [<<"validate_doc_update">>], Args]}, _
Msg = [<<"validate_doc_update">>, DDocId],
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St};
Selector ->
[NewDoc, OldDoc, _Ctx, _SecObj] = Args,
Struct = {[{<<"newDoc">>, NewDoc}, {<<"oldDoc">>, OldDoc}]},
Reply =
case mango_selector:match(Selector, Struct) of
true -> true;
_ -> {[{<<"forbidden">>, <<"document is not valid">>}]}
end,
{reply, Reply, St}
case mango_selector:has_allowed_fields(Selector, [<<"newDoc">>, <<"oldDoc">>]) of
false ->
Msg =
<<"'validate_doc_update' may only contain 'newDoc' and 'oldDoc' as top-level fields">>,
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St};
true ->
[NewDoc, OldDoc, _Ctx, _SecObj] = Args,
Struct =
case OldDoc of
null -> {[{<<"newDoc">>, NewDoc}]};
Doc -> {[{<<"newDoc">>, NewDoc}, {<<"oldDoc">>, Doc}]}
end,
Reply =
case mango_selector:match_failures(Selector, Struct) of
[] ->
true;
Failures ->
{[
{<<"forbidden">>, <<"forbidden">>},
{<<"failures">>, Failures}
]}
end,
{reply, Reply, St}
end
end;
handle_call(Msg, _From, St) ->
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
Expand Down
Loading