Reject design docs with compilation errors

If we know the language for a given design doc we'll attempt to compile
the map/reduce functions and reject updates that introduce code that
doesn't compile. We don't yet do the other types of functions because
those errors are more user visible.
diff --git a/src/couch_mrview.erl b/src/couch_mrview.erl
index 20ba6d9..29b86d7 100644
--- a/src/couch_mrview.erl
+++ b/src/couch_mrview.erl
@@ -12,6 +12,7 @@
 
 -module(couch_mrview).
 
+-export([validate/2]).
 -export([query_all_docs/2, query_all_docs/4]).
 -export([query_view/3, query_view/4, query_view/6]).
 -export([get_info/2]).
@@ -40,6 +41,40 @@
 }).
 
 
+validate(DbName, DDoc) ->
+    GetName = fun
+        (#mrview{map_names = [Name | _]}) -> Name;
+        (#mrview{reduce_funs = [{Name, _} | _]}) -> Name;
+        (_) -> null
+    end,
+    ValidateView = fun(Proc, #mrview{def=MapSrc, reduce_funs=Reds}=View) ->
+        couch_query_servers:try_compile(Proc, map, GetName(View), MapSrc),
+        lists:foreach(fun
+            ({_RedName, <<"_", _/binary>>}) ->
+                ok;
+            ({RedName, RedSrc}) ->
+                couch_query_servers:try_compile(Proc, reduce, RedName, RedSrc)
+        end, Reds)
+    end,
+    {ok, #mrst{language=Lang, views=Views}}
+            = couch_mrview_util:ddoc_to_mrst(DbName, DDoc),
+    try Views =/= [] andalso couch_query_servers:get_os_process(Lang) of
+        false ->
+            ok;
+        Proc ->
+            try
+                lists:foreach(fun(V) -> ValidateView(Proc, V) end, Views)
+            catch Error ->
+                Error
+            after
+                couch_query_servers:ret_os_process(Proc)
+            end
+    catch {unknown_query_language, _Lang} ->
+        %% Allow users to save ddocs written in uknown languages
+        ok
+    end.
+
+
 query_all_docs(Db, Args) ->
     query_all_docs(Db, Args, fun default_cb/2, []).