Skip to content

Commit 28ee299

Browse files
authored
Merge pull request #5839 from neighbourhoodie/mango-vdu
Mango vdu
2 parents f519c1b + ae57d85 commit 28ee299

7 files changed

Lines changed: 341 additions & 14 deletions

File tree

src/couch/src/couch_query_servers.erl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -477,18 +477,18 @@ builtin_cmp_last(A, B) ->
477477
validate_doc_update(Db, DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
478478
JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
479479
JsonDiskDoc = json_doc(DiskDoc),
480-
Resp = ddoc_prompt(
481-
Db,
482-
DDoc,
483-
[<<"validate_doc_update">>],
484-
[JsonEditDoc, JsonDiskDoc, Ctx, SecObj]
485-
),
486-
if
487-
Resp == 1 -> ok;
488-
true -> couch_stats:increment_counter([couchdb, query_server, vdu_rejects], 1)
489-
end,
480+
Args = [JsonEditDoc, JsonDiskDoc, Ctx, SecObj],
481+
482+
Resp =
483+
case ddoc_prompt(Db, DDoc, [<<"validate_doc_update">>], Args) of
484+
Code when Code =:= 1; Code =:= ok; Code =:= true ->
485+
ok;
486+
Other ->
487+
couch_stats:increment_counter([couchdb, query_server, vdu_rejects], 1),
488+
Other
489+
end,
490490
case Resp of
491-
RespCode when RespCode =:= 1; RespCode =:= ok; RespCode =:= true ->
491+
ok ->
492492
ok;
493493
{[{<<"forbidden">>, Message}]} ->
494494
throw({forbidden, Message});

src/couch_mrview/src/couch_mrview.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ validate_ddoc_fields(DDoc) ->
6262
[{<<"rewrites">>, [string, array]}],
6363
[{<<"shows">>, object}, {any, [object, string]}],
6464
[{<<"updates">>, object}, {any, [object, string]}],
65-
[{<<"validate_doc_update">>, string}],
65+
[{<<"validate_doc_update">>, [string, object]}],
6666
[{<<"views">>, object}, {<<"lib">>, object}],
6767
[{<<"views">>, object}, {any, object}, {<<"map">>, MapFuncType}],
6868
[{<<"views">>, object}, {any, object}, {<<"reduce">>, string}]

src/docs/src/api/ddoc/common.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@
5454
* **rewrites** (*array* or *string*): Rewrite rules definition. *Deprecated.*
5555
* **shows** (*object*): :ref:`Show functions <showfun>` definition. *Deprecated.*
5656
* **updates** (*object*): :ref:`Update functions <updatefun>` definition
57-
* **validate_doc_update** (*string*): :ref:`Validate document update
58-
<vdufun>` function source
57+
* **validate_doc_update** (*string* or *object*): :ref:`Validate document
58+
update <vdufun>` JavaScript function source, or :ref:`Mango selector
59+
<find/selectors>`
5960
* **views** (*object*): :ref:`View functions <viewfun>` definition.
6061
* **autoupdate** (*boolean*): Indicates whether to automatically build
6162
indexes defined in this design document. Default is ``true``.

src/docs/src/ddocs/ddocs.rst

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,3 +937,78 @@ modified by a user with the ``_admin`` role:
937937
CouchDB Guide:
938938
- `Validation Functions
939939
<http://guide.couchdb.org/editions/1/en/validation.html>`_
940+
941+
Validation using Mango selectors
942+
--------------------------------
943+
944+
The ``validate_doc_update`` field may be written as a :ref:`Mango selector
945+
<find/selectors>`, instead of as a JavaScript function. This provides greater
946+
performance since documents do not need to be sent to an external process for
947+
validation, but is more restrictive in terms of what kinds of validation rules
948+
can be expressed. Mango selectors can express declarative rules about the
949+
strucure of the existing document stored on disk, and the new version of the
950+
document; the document must match the given selector in order for the update to
951+
be accepted.
952+
953+
To use Mango selectors for validation, the design document must have the
954+
``language`` field set to ``query``. The selector is applied to a JSON structure
955+
containing the following fields:
956+
957+
* ``newDoc``: New version of document that will be stored.
958+
* ``oldDoc``: Previous version of document that is already stored.
959+
960+
For example, to check that all docs contain a ``title`` which is a string, and a
961+
``year`` which is a number:
962+
963+
.. code-block:: json
964+
965+
{
966+
"language": "query",
967+
968+
"validate_doc_update": {
969+
"newDoc": {
970+
"title": { "$type": "string" },
971+
"year": { "$type": "number" }
972+
}
973+
}
974+
}
975+
976+
All the features of Mango selectors are supported here, so any condition that
977+
can be expressed as a selector can be implemented in this way. Operators like
978+
``$lt`` and ``$gt`` can be used to restrict values to a given range,
979+
``$allMatch`` can be used to check all the items in an array match some schema,
980+
and it is even possible to implement conditional checks using logical
981+
combinators.
982+
983+
For example, say we want documents with ``"type": "movie"`` to have a ``title``
984+
and ``year`` as above, and documents with ``"type": "actor"`` to have a ``name``
985+
and a non-empty list of strings under ``movies``. This can be achieved using
986+
this design document:
987+
988+
.. code-block:: json
989+
990+
{
991+
"language": "query",
992+
993+
"validate_doc_update": {
994+
"newDoc": {
995+
"type": { "$in": ["movie", "actor"] },
996+
"$or": [
997+
{
998+
"type": "movie",
999+
"title": { "$type": "string" },
1000+
"year": { "$type": "number" }
1001+
},
1002+
{
1003+
"type": "actor",
1004+
"name": { "$type": "string" },
1005+
"movies": {
1006+
"$type": "array",
1007+
"$not": { "$size": 0 },
1008+
"$allMatch": { "$type": "string" }
1009+
}
1010+
}
1011+
}
1012+
}
1013+
}
1014+
}

src/mango/src/mango_native_proc.erl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
-record(st, {
3131
indexes = [],
32+
validators = [],
3233
timeout = 5000
3334
}).
3435

@@ -94,6 +95,32 @@ handle_call({prompt, [<<"nouveau_index_doc">>, Doc]}, _From, St) ->
9495
Else
9596
end,
9697
{reply, Vals, St};
98+
handle_call({prompt, [<<"ddoc">>, <<"new">>, DDocId, {DDoc}]}, _From, St) ->
99+
NewSt =
100+
case couch_util:get_value(<<"validate_doc_update">>, DDoc) of
101+
undefined ->
102+
St;
103+
Selector0 ->
104+
Selector = mango_selector:normalize(Selector0),
105+
Validators = couch_util:set_value(DDocId, St#st.validators, Selector),
106+
St#st{validators = Validators}
107+
end,
108+
{reply, true, NewSt};
109+
handle_call({prompt, [<<"ddoc">>, DDocId, [<<"validate_doc_update">>], Args]}, _From, St) ->
110+
case couch_util:get_value(DDocId, St#st.validators) of
111+
undefined ->
112+
Msg = [<<"validate_doc_update">>, DDocId],
113+
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St};
114+
Selector ->
115+
[NewDoc, OldDoc, _Ctx, _SecObj] = Args,
116+
Struct = {[{<<"newDoc">>, NewDoc}, {<<"oldDoc">>, OldDoc}]},
117+
Reply =
118+
case mango_selector:match(Selector, Struct) of
119+
true -> true;
120+
_ -> {[{<<"forbidden">>, <<"document is not valid">>}]}
121+
end,
122+
{reply, Reply, St}
123+
end;
97124
handle_call(Msg, _From, St) ->
98125
{stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
99126

test/elixir/test/config/suite.elixir

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,18 @@
521521
"serial execution is not spuriously counted as loop on test_rewrite_suite_db",
522522
"serial execution is not spuriously counted as loop on test_rewrite_suite_db%2Fwith_slashes"
523523
],
524+
"ValidateDocUpdateTest": [
525+
"JavaScript VDU accepts a valid document",
526+
"JavaScript VDU rejects an invalid document",
527+
"JavaScript VDU accepts a valid change",
528+
"JavaScript VDU rejects an invalid change",
529+
"Mango VDU accepts a valid document",
530+
"Mango VDU rejects an invalid document",
531+
"updating a Mango VDU updates its effects",
532+
"converting a Mango VDU to JavaScript updates its effects",
533+
"deleting a Mango VDU removes its effects",
534+
"Mango VDU rejects a doc if any existing ddoc fails to match",
535+
],
524536
"SecurityValidationTest": [
525537
"Author presence and user security",
526538
"Author presence and user security when replicated",

0 commit comments

Comments
 (0)