You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2008/11/11 20:45:51 UTC

svn commit: r713132 - in /incubator/couchdb/trunk: share/server/ share/www/script/ src/couchdb/

Author: damien
Date: Tue Nov 11 11:45:50 2008
New Revision: 713132

URL: http://svn.apache.org/viewvc?rev=713132&view=rev
Log:
Check in of initial validation and authorization work. This work is incomplete, as there is not yet any way of restricting who can update the design docs.

Modified:
    incubator/couchdb/trunk/share/server/main.js
    incubator/couchdb/trunk/share/www/script/couch.js
    incubator/couchdb/trunk/share/www/script/couch_tests.js
    incubator/couchdb/trunk/src/couchdb/couch_db.erl
    incubator/couchdb/trunk/src/couchdb/couch_db.hrl
    incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl
    incubator/couchdb/trunk/src/couchdb/couch_doc.erl
    incubator/couchdb/trunk/src/couchdb/couch_erl_driver.c
    incubator/couchdb/trunk/src/couchdb/couch_httpd.erl
    incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl
    incubator/couchdb/trunk/src/couchdb/couch_key_tree.erl
    incubator/couchdb/trunk/src/couchdb/couch_query_servers.erl
    incubator/couchdb/trunk/src/couchdb/couch_rep.erl
    incubator/couchdb/trunk/src/couchdb/couch_server.erl

Modified: incubator/couchdb/trunk/share/server/main.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/server/main.js?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/share/server/main.js [utf-8] (original)
+++ incubator/couchdb/trunk/share/server/main.js [utf-8] Tue Nov 11 11:45:50 2008
@@ -153,7 +153,19 @@
         print("[true," + toJSON(reductions) + "]");
         }
         break;
-
+      case "validate":
+        var funSrc = cmd[1];
+        var newDoc = cmd[2];
+        var oldDoc = cmd[3];
+        var userCtx = cmd[4];
+        var validateFun = compileFunction(funSrc);
+        try {
+          validateFun(newDoc, oldDoc, userCtx);
+          print("1");
+        } catch (error) {
+          print(toJSON(error));
+        }
+        break;
       default:
         print(toJSON({error: "query_server_error",
             reason: "unknown command '" + cmd[0] + "'"}));

Modified: incubator/couchdb/trunk/share/www/script/couch.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch.js?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/share/www/script/couch.js [utf-8] (original)
+++ incubator/couchdb/trunk/share/www/script/couch.js [utf-8] Tue Nov 11 11:45:50 2008
@@ -13,18 +13,18 @@
 // A simple class to represent a database. Uses XMLHttpRequest to interface with
 // the CouchDB server.
 
-function CouchDB(name) {
-  this.name = name
+function CouchDB(name, options) {
+  this.name = name;
   this.uri = "/" + encodeURIComponent(name) + "/";
-  request = CouchDB.request;
+  request = function(method, uri, requestOptions) {
+      return CouchDB.request(method, uri, combine(requestOptions, options));
+    }
 
   // Creates the database on the server
   this.createDb = function() {
     var req = request("PUT", this.uri);
-    var result = JSON.parse(req.responseText);
-    if (req.status != 201)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   // Deletes the database on the server
@@ -32,10 +32,8 @@
     var req = request("DELETE", this.uri);
     if (req.status == 404)
       return false;
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   // Save a document to the database
@@ -47,9 +45,8 @@
     req = request("PUT", this.uri  + encodeURIComponent(doc._id) + encodeOptions(options), {
       body: JSON.stringify(doc)
     });
+    maybeThrowError(req);
     var result = JSON.parse(req.responseText);
-    if (req.status != 201)
-      throw result;
     doc._rev = result.rev;
     return result;
   }
@@ -59,18 +56,15 @@
     var req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
     if (req.status == 404)
       return null;
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   // Deletes a document from the database
   this.deleteDoc = function(doc) {
     var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
+    maybeThrowError(req);
     var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
     doc._rev = result.rev; //record rev in input document
     doc._deleted = true;
     return result;
@@ -79,9 +73,8 @@
   // Deletes an attachment from a document
   this.deleteDocAttachment = function(doc, attachment_name) {
     var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name + "?rev=" + doc._rev);
+    maybeThrowError(req);
     var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
     doc._rev = result.rev; //record rev in input document
     return result;
   }
@@ -102,9 +95,8 @@
     var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
       body: JSON.stringify({"docs": docs})
     });
+    maybeThrowError(req);
     var result = JSON.parse(req.responseText);
-    if (req.status != 201)
-      throw result;
     for (var i = 0; i < docs.length; i++) {
         docs[i]._rev = result.new_revs[i].rev;
     }
@@ -129,10 +121,8 @@
       headers: {"Content-Type": "application/json"},
       body: JSON.stringify(body)
     });
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   this.view = function(viewname, options, keys) {
@@ -147,19 +137,15 @@
     }
     if (req.status == 404)
       return null;
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   // gets information about the database
   this.info = function() {
     var req = request("GET", this.uri);
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   this.allDocs = function(options,keys) {
@@ -172,18 +158,14 @@
         body: JSON.stringify({keys:keys})
       });      
     }
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   this.compact = function() {
     var req = request("POST", this.uri + "_compact");
-    var result = JSON.parse(req.responseText);
-    if (req.status != 202)
-      throw result;
-    return result;
+    maybeThrowError(req);
+    return JSON.parse(req.responseText);
   }
 
   // Convert a options object to an url query string.
@@ -209,6 +191,35 @@
   function toJSON(obj) {
     return obj !== null ? JSON.stringify(obj) : null;
   }
+  
+  function combine(object1, object2) {
+    if (!object2) {
+      return object1;
+    }
+    if (!object1) {
+      return object2;
+    }
+    for (var name in object2) {
+      object1[name] = object2[name];
+    }
+    return object1;
+  }
+  
+  function maybeThrowError(req) {
+    if (req.status >= 400) {
+      if (req.responseText) {
+        try {
+          var result = JSON.parse(req.responseText);
+        } catch (ParseError) {
+          var result = {error:"unknown", reason:req.responseText};
+        }
+      } else {
+        var result = {};
+      }
+      result.http_status = req.status;
+      throw result;
+    }
+  }
 }
 
 CouchDB.allDbs = function() {
@@ -247,7 +258,7 @@
   } else {
     throw new Error("No XMLHTTPRequest support detected");
   }
-  req.open(method, uri, false);
+  req.open(method, uri, false, options.username, options.password);
   if (options.headers) {
     var headers = options.headers;
     for (var headerName in headers) {

Modified: incubator/couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch_tests.js?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] Tue Nov 11 11:45:50 2008
@@ -1918,6 +1918,65 @@
     // you can get a single key
     xhr = CouchDB.request("GET", "/_config/test/foo");
     T(xhr.responseText == '"bar"');
+  },
+  
+  security : function(debug) {
+    var db = new CouchDB("test_suite_db");
+    db.deleteDb();
+    db.createDb();
+    if (debug) debugger;
+
+    var designDoc = {
+      _id:"_design/test",
+      language: "javascript",
+      validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) {
+        // docs should have an author field.
+        if (!newDoc.author) {
+          throw {error:"forbidden",
+                reason:"Documents must have an author field",
+                http_status:403};
+        }
+        if (oldDoc && oldDoc.author != userCtx.name) {
+            throw {error:"unauthorized",
+                reason:"You are not the author of this document. You jerk.",
+                headers:
+                  {"WWW-Authenticate": "Basic realm=\"" + userCtx.db + "\""},
+                http_status:401};
+        }
+      }).toString() + ")"
+    }
+    
+    db.save(designDoc);
+    
+    var userDb = new CouchDB("test_suite_db", {username:"test user", password:"foo"});
+    
+    try {
+      userDb.save({foo:1});
+      T(false && "Can't get here. Should have thrown an error");
+    } catch (e) {
+      T(e.error == "forbidden");
+      T(e.http_status == 403);
+    }
+    
+    userDb.save({_id:"testdoc", foo:1, author:"test user"});
+    
+    var doc = userDb.open("testdoc");
+    doc.foo=2;
+    userDb.save(doc);
+    
+    var user2Db = new CouchDB("test_suite_db", {username:"test user2"});
+    
+    var doc = user2Db.open("testdoc");
+    doc.foo=3;
+    try {
+      user2Db.save(doc);
+      T(false && "Can't get here. Should have thrown an error 2");
+    } catch (e) {
+      T(e.error == "unauthorized");
+      T(e.http_status == 401);
+    }
+    
+    
   }
 };
 

Modified: incubator/couchdb/trunk/src/couchdb/couch_db.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_db.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_db.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_db.erl Tue Nov 11 11:45:50 2008
@@ -14,14 +14,14 @@
 -behaviour(gen_server).
 
 -export([open/2,close/1,create/2,start_compact/1,get_db_info/1]).
--export([open_ref_counted/2,num_refs/1,monitor/1]).
--export([save_docs/3,update_doc/3,update_docs/2,update_docs/3,delete_doc/3]).
+-export([open_ref_counted/3,num_refs/1,monitor/1]).
+-export([update_doc/3,update_docs/4,update_docs/2,update_docs/3,delete_doc/3]).
 -export([get_doc_info/2,open_doc/2,open_doc/3,open_doc_revs/4]).
--export([get_missing_revs/2,name/1]).
+-export([get_missing_revs/2,name/1,doc_to_tree/1]).
 -export([enum_docs/4,enum_docs/5,enum_docs_since/4,enum_docs_since/5]).
 -export([enum_docs_since_reduce_to_count/1,enum_docs_reduce_to_count/1]).
 -export([increment_update_seq/1,get_purge_seq/1,purge_docs/2,get_last_purged/1]).
--export([start_link/3]).
+-export([start_link/3,make_doc/2]).
 -export([init/1,terminate/2,handle_call/3,handle_cast/2,code_change/3,handle_info/2]).
 
 
@@ -33,19 +33,7 @@
     {ok, Fd} ->
         StartResult = gen_server:start_link(couch_db, {DbName, Filepath, Fd, Options}, []),
         unlink(Fd),
-        case StartResult of
-        {ok, _} ->
-            % We successfully opened the db, delete old storage files if around
-            case file:delete(Filepath ++ ".old") of
-            ok ->
-                ?LOG_INFO("Deleted old storage file ~s~s", [Filepath, ".old"]);
-            {error, enoent} ->
-                ok  % normal result
-            end,
-            StartResult;
-        Error ->
-            Error
-        end;
+        StartResult;
     Else ->
         Else
     end.
@@ -79,8 +67,9 @@
 close(#db{fd=Fd}) ->
     couch_file:drop_ref(Fd).
 
-open_ref_counted(MainPid, OpeningPid) ->
-    gen_server:call(MainPid, {open_ref_counted_instance, OpeningPid}).
+open_ref_counted(MainPid, OpeningPid, UserCred) ->
+    {ok, Db} = gen_server:call(MainPid, {open_ref_counted_instance, OpeningPid}),
+    {ok, Db#db{user_ctx=UserCred}}.
 
 num_refs(MainPid) ->
     gen_server:call(MainPid, num_refs).
@@ -213,39 +202,92 @@
         % add to new bucket
        group_alike_docs(Rest, [[Doc]|[Bucket|RestBuckets]])
     end.
-    
 
-prepare_doc_for_new_edit(Db, #doc{id=Id,revs=[NewRev|PrevRevs]}=Doc, OldFullDocInfo, LeafRevsDict) ->
+validate_doc_update(#db{validate_doc_funs=[]}, Doc, _GetDiskDocFun) ->
+    Doc;
+validate_doc_update(_Db, #doc{id= <<"_design/",_/binary>>}=Doc, _GetDiskDocFun) ->
+    Doc;
+validate_doc_update(_Db, #doc{id= <<"_local/",_/binary>>}=Doc, _GetDiskDocFun) ->
+    Doc;
+validate_doc_update(#db{name=DbName,user_ctx={CtxProps}}=Db, Doc, GetDiskDocFun) ->
+    DiskDoc = GetDiskDocFun(),
+    [case Fun(Doc, DiskDoc, {[{<<"db">>, DbName} | CtxProps]}) of
+        ok -> ok;
+        Error -> throw(Error)
+    end || Fun <- Db#db.validate_doc_funs],
+    Doc.
+
+
+prep_and_validate_new_edit(Db, #doc{id=Id,revs=[NewRev|PrevRevs]}=Doc,
+        OldFullDocInfo, LeafRevsDict) ->
     case PrevRevs of
     [PrevRev|_] ->
         case dict:find(PrevRev, LeafRevsDict) of
         {ok, {Deleted, Sp, DiskRevs}} ->
-            case couch_doc:has_stubs(Doc) of
+            Doc2 = Doc#doc{revs=[NewRev|DiskRevs]},
+            case couch_doc:has_stubs(Doc2) of
             true ->
                 DiskDoc = make_doc(Db, Id, Deleted, Sp, DiskRevs),
-                Doc2 = couch_doc:merge_stubs(Doc, DiskDoc),
-                Doc2#doc{revs=[NewRev|DiskRevs]};
+                Doc3 = couch_doc:merge_stubs(Doc2, DiskDoc),
+                validate_doc_update(Db, Doc3, fun() -> DiskDoc end);
             false ->
-                Doc#doc{revs=[NewRev|DiskRevs]}
+                LoadDiskDoc = fun() -> make_doc(Db,Id,Deleted,Sp,DiskRevs) end,
+                validate_doc_update(Db, Doc2, LoadDiskDoc)
             end;
         error ->
             throw(conflict)
         end;
     [] ->
-        % new doc, and we have existing revs. 
-        OldDocInfo = couch_doc:to_doc_info(OldFullDocInfo),
-        if OldDocInfo#doc_info.deleted ->
-            % existing doc is a deleton
-            % allow this new doc to be a later revision.
-            {_Deleted, _Sp, Revs} = dict:fetch(OldDocInfo#doc_info.rev, LeafRevsDict),
-            Doc#doc{revs=[NewRev|Revs]};
+        % new doc, and we have existing revs.
+        if OldFullDocInfo#full_doc_info.deleted ->
+            % existing docs are deletions
+            validate_doc_update(Db, Doc, nil);
         true ->
             throw(conflict)
         end
     end.
 
 update_docs(#db{update_pid=UpdatePid}=Db, Docs, Options) ->
-    % go ahead and generate the new revision ids for the documents.
+    update_docs(#db{update_pid=UpdatePid}=Db, Docs, Options, true).
+
+update_docs(Db, Docs, Options, false) ->
+    DocBuckets = group_alike_docs(Docs),
+    Ids = [Id || [#doc{id=Id}|_] <- DocBuckets],
+    
+    ExistingDocs = get_full_doc_infos(Db, Ids),
+    
+    DocBuckets2 = lists:zipwith(
+        fun(Bucket, not_found) ->
+            [validate_doc_update(Db, Doc, fun()-> nil end) || Doc <- Bucket];
+        (Bucket, {ok, #full_doc_info{rev_tree=OldRevTree}}) ->
+            NewTree = lists:foldl(
+                fun(Doc, RevTreeAcc) ->
+                    couch_key_tree:merge(RevTreeAcc, doc_to_tree(Doc))
+                end,
+                OldRevTree, Bucket),
+            Leafs = couch_key_tree:get_all_leafs_full(NewTree),
+            LeafRevsFullDict = dict:from_list( [{Rev, FullPath} || [{Rev, _}|_]=FullPath <- Leafs]),
+            lists:flatmap(
+                fun(#doc{revs=[Rev|_]}=Doc) ->
+                    case dict:find(Rev, LeafRevsFullDict) of
+                    {ok, [{Rev, #doc{id=Id}}|_]=Path} ->
+                        % our unflushed doc is a leaf node. Go back on the path 
+                        % to find the previous rev that's on disk.
+                        LoadPrevRev = fun() ->
+                            make_first_doc_on_disk(Db, Id, Path)
+                        end,
+                        [validate_doc_update(Db, Doc, LoadPrevRev)];
+                    _ ->
+                        % this doc isn't a leaf or is already exists in the tree. ignore
+                        []
+                    end
+                end, Bucket)
+        end,
+        DocBuckets, ExistingDocs),
+    write_and_commit(Db, DocBuckets2, Options);
+    
+update_docs(Db, Docs, Options, true) ->
+        % go ahead and generate the new revision ids for the documents.
     Docs2 = lists:map(
         fun(#doc{id=Id,revs=Revs}=Doc) ->
             case Id of
@@ -256,7 +298,6 @@
                 Doc#doc{revs=[list_to_binary(integer_to_list(couch_util:rand32())) | Revs]}
             end
         end, Docs),
-    NewRevs = [NewRev || #doc{revs=[NewRev|_]} <- Docs2],
     DocBuckets = group_alike_docs(Docs2),
     Ids = [Id || [#doc{id=Id}|_] <- DocBuckets],
     
@@ -266,39 +307,51 @@
     
     DocBuckets2 = lists:zipwith(
         fun(Bucket, not_found) ->
-            % no existing revs, make sure no old revision is specified.
+            % no existing revs on disk, make sure no old revs specified.
             [throw(conflict) || #doc{revs=[_NewRev, _OldRev | _]} <- Bucket],
-            Bucket;
+            [validate_doc_update(Db, Doc, fun()-> nil end) || Doc <- Bucket];
         (Bucket, {ok, #full_doc_info{rev_tree=OldRevTree}=OldFullDocInfo}) ->
             Leafs = couch_key_tree:get_all_leafs(OldRevTree),
             LeafRevsDict = dict:from_list([{Rev, {Deleted, Sp, Revs}} || {Rev, {Deleted, Sp}, Revs} <- Leafs]),
-            [prepare_doc_for_new_edit(Db, Doc, OldFullDocInfo, LeafRevsDict) || Doc <- Bucket]
+            [prep_and_validate_new_edit(Db, Doc, OldFullDocInfo, LeafRevsDict) || Doc <- Bucket]
         end,
         DocBuckets, ExistingDocs),
-    % flush unwritten binaries to disk.
-    DocBuckets3 = [[doc_flush_binaries(Doc, Db#db.fd) || Doc <- Bucket] || Bucket <- DocBuckets2],
+    ok = write_and_commit(Db, DocBuckets2, [new_edits | Options]),
+    {ok, [NewRev ||#doc{revs=[NewRev|_]} <- Docs2]}.
+
+
+% Returns the first available document on disk. Input list is a full rev path
+% for the doc.
+make_first_doc_on_disk(_Db, _Id, []) ->
+    nil;
+make_first_doc_on_disk(Db, Id, [{_Rev, ?REV_MISSING}|RestPath]) ->
+    make_first_doc_on_disk(Db, Id, RestPath);
+make_first_doc_on_disk(Db, Id, [{_Rev, {IsDel, Sp}} |_]=DocPath) ->
+    Revs = [Rev || {Rev, _} <- DocPath],
+    make_doc(Db, Id, IsDel, Sp, Revs).
+
+
+write_and_commit(#db{update_pid=UpdatePid}=Db, DocBuckets, Options) ->
 
-    case gen_server:call(UpdatePid, {update_docs, DocBuckets3, [new_edits | Options]}, infinity) of
-    ok -> {ok, NewRevs};
+    % flush unwritten binaries to disk.
+    DocBuckets2 = [[doc_flush_binaries(Doc, Db#db.fd) || Doc <- Bucket] || Bucket <- DocBuckets],
+    case gen_server:call(UpdatePid, {update_docs, DocBuckets2, Options}, infinity) of
+    ok -> ok;
     retry ->
-        {ok, Db2} = open_ref_counted(Db#db.main_pid, self()),
-        DocBuckets4 = [[doc_flush_binaries(Doc, Db2#db.fd) || Doc <- Bucket] || Bucket <- DocBuckets3],
+        % This can happen if the db file we wrote to was swapped out by
+        % compaction. Retry writing to the current file
+        {ok, Db2} = open_ref_counted(Db#db.main_pid, self(), {[]}),
+        DocBuckets3 = [[doc_flush_binaries(Doc, Db2#db.fd) || Doc <- Bucket] || Bucket <- DocBuckets],
         % We only retry once
         close(Db2),
-        case gen_server:call(UpdatePid, {update_docs, DocBuckets4, [new_edits | Options]}, infinity) of
-        ok -> {ok, NewRevs};
+        case gen_server:call(UpdatePid, {update_docs, DocBuckets3, Options}, infinity) of
+        ok -> ok;
         Else -> throw(Else)
         end;
     Else->
         throw(Else)
     end.
 
-save_docs(#db{update_pid=UpdatePid, fd=Fd}, Docs, Options) ->
-    % flush unwritten binaries to disk.
-    DocBuckets = group_alike_docs(Docs),
-    DocBuckets2 = [[doc_flush_binaries(Doc, Fd) || Doc <- Bucket] || Bucket <- DocBuckets],
-    ok = gen_server:call(UpdatePid, {update_docs, DocBuckets2, Options}, infinity).
-
 
 doc_flush_binaries(Doc, Fd) ->
     % calc size of binaries to write out
@@ -509,14 +562,30 @@
         end
     end.
 
-make_doc(Db, Id, Deleted, SummaryPointer, RevisionPath) ->
+
+doc_to_tree(Doc) ->
+    doc_to_tree(Doc, lists:reverse(Doc#doc.revs)).
+
+doc_to_tree(Doc, [RevId]) ->
+    [{RevId, Doc, []}];
+doc_to_tree(Doc, [RevId | Rest]) ->
+    [{RevId, ?REV_MISSING, doc_to_tree(Doc, Rest)}].
+
+make_doc(Db, FullDocInfo) ->
+    {#doc_info{id=Id,deleted=Deleted,summary_pointer=Sp}, RevPath}
+            = couch_doc:to_doc_info_path(FullDocInfo),
+    make_doc(Db, Id, Deleted, Sp, RevPath).
+    
+make_doc(#db{fd=Fd}=Db, Id, Deleted, BodySp, RevisionPath) ->
     {BodyData, BinValues} =
-    case SummaryPointer of
+    case BodySp of
     nil ->
         {[], []};
     _ ->
-        {ok, {BodyData0, BinValues0}} = couch_stream:read_term(Db#db.summary_stream, SummaryPointer),
-        {BodyData0, [{Name, {Type, {Db#db.fd, Sp, Len}}} || {Name, {Type, Sp, Len}} <- BinValues0]}   
+        {ok, {BodyData0, BinValues0}} =
+            couch_stream:read_term( Db#db.summary_stream, BodySp),
+        {BodyData0,
+            [{Name,{Type,{Fd,Sp,Len}}} || {Name,{Type,Sp,Len}} <- BinValues0]}
     end,
     #doc{
         id = Id,

Modified: incubator/couchdb/trunk/src/couchdb/couch_db.hrl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_db.hrl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_db.hrl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_db.hrl Tue Nov 11 11:45:50 2008
@@ -111,7 +111,9 @@
     local_docs_btree,
     update_seq,
     name,
-    filepath
+    filepath,
+    validate_doc_funs=[],
+    user_ctx={[]}
     }).
     
 

Modified: incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl Tue Nov 11 11:45:50 2008
@@ -28,13 +28,13 @@
         Header =  #db_header{},
         ok = couch_file:write_header(Fd, ?HEADER_SIG, Header),
         % delete any old compaction files that might be hanging around
-        file:delete(Filepath ++ ".compact"),
-        file:delete(Filepath ++ ".old");
+        file:delete(Filepath ++ ".compact");
     false ->
         {ok, Header} = couch_file:read_header(Fd, ?HEADER_SIG)
     end,
     
     Db = init_db(DbName, Filepath, Fd, Header),
+    Db2 = refresh_validate_doc_funs(Db),
     {ok, Db#db{main_pid=MainPid}}.
 
 terminate(_Reason, Db) ->
@@ -158,7 +158,6 @@
         
         couch_stream:close(Db#db.summary_stream),
         couch_file:close_maybe(Db#db.fd),
-        file:delete(Filepath ++ ".old"),
             
         ok = gen_server:call(Db#db.main_pid, {db_updated, NewDb2}),
         ?LOG_INFO("Compaction for db \"~s\" completed.", [Db#db.name]),
@@ -254,21 +253,34 @@
         local_docs_btree = LocalDocsBtree,
         update_seq = Header#db_header.update_seq,
         name = DbName,
-        filepath=Filepath }.
+        filepath=Filepath}.
+
 
 close_db(#db{fd=Fd,summary_stream=Ss}) ->
     couch_file:close(Fd),
     couch_stream:close(Ss).
 
-% rev tree functions
+refresh_validate_doc_funs(Db) ->
+    {ok, DesignDocs} = get_design_docs(Db),
+    ProcessDocFuns = lists:flatmap(
+        fun(DesignDoc) ->
+            case couch_doc:get_validate_doc_fun(DesignDoc) of
+            nil -> [];
+            Fun -> [Fun]
+            end
+        end, DesignDocs),
+    Db#db{validate_doc_funs=ProcessDocFuns}.
 
-doc_to_tree(Doc) ->
-    doc_to_tree(Doc, lists:reverse(Doc#doc.revs)).
+get_design_docs(#db{fulldocinfo_by_id_btree=Btree}=Db) ->
+    couch_btree:foldl(Btree, <<"_design/">>,
+        fun(#full_doc_info{id= <<"_design/",_/binary>>}=FullDocInfo, _Reds, AccDocs) ->
+            {ok, [couch_db:make_doc(Db, FullDocInfo) | AccDocs]};
+        (_, _Reds, AccDocs) ->
+            {stop, AccDocs}
+        end,
+        []).
 
-doc_to_tree(Doc, [RevId]) ->
-    [{RevId, Doc, []}];
-doc_to_tree(Doc, [RevId | Rest]) ->
-    [{RevId, ?REV_MISSING, doc_to_tree(Doc, Rest)}].
+% rev tree functions
 
 flush_trees(_Db, [], AccFlushedTrees) ->
     {ok, lists:reverse(AccFlushedTrees)};
@@ -311,7 +323,7 @@
     #full_doc_info{id=Id,rev_tree=OldTree}=OldDocInfo,
     UpdatesRevTree = lists:foldl(
         fun(NewDoc, AccTree) ->
-            couch_key_tree:merge(AccTree, doc_to_tree(NewDoc))
+            couch_key_tree:merge(AccTree, couch_db:doc_to_tree(NewDoc))
         end,
         [], NewDocs),
     NewRevTree = couch_key_tree:merge(OldTree, UpdatesRevTree),
@@ -323,7 +335,14 @@
             OldConflicts = couch_key_tree:count_leafs(OldTree),
             NewConflicts = couch_key_tree:count_leafs(NewRevTree),
             if NewConflicts > OldConflicts ->
-                throw(conflict);
+                % if all the old docs are deletions, allow this new conflict
+                case [1 || {_Rev,{IsDel,_Sp}} <- 
+                    couch_key_tree:get_all_leafs(OldTree), IsDel==false] of
+                [] ->
+                    ok;
+                _ ->
+                    throw(conflict)
+                end;
             true -> ok
             end;
         true -> ok
@@ -397,12 +416,19 @@
         fulldocinfo_by_id_btree = DocInfoByIdBTree2,
         docinfo_by_seq_btree = DocInfoBySeqBTree2,
         update_seq = NewSeq},
+    
+    case [1 || <<"_design/",_/binary>> <- Ids] of
+    [] ->
+        Db4 = Db3;
+    _ ->
+        Db4 = refresh_validate_doc_funs(Db3)
+    end,
 
     case lists:member(delay_commit, Options) of
     true ->
-        {ok, Db3};
+        {ok, Db4};
     false ->
-        {ok, commit_data(Db3)}
+        {ok, commit_data(Db4)}
     end.
 
 update_local_docs(#db{local_docs_btree=Btree}=Db, Docs) ->

Modified: incubator/couchdb/trunk/src/couchdb/couch_doc.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_doc.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_doc.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_doc.erl Tue Nov 11 11:45:50 2008
@@ -12,8 +12,8 @@
 
 -module(couch_doc).
 
--export([get_view_functions/1, is_special_doc/1,to_doc_info/1]).
--export([bin_foldl/3,bin_size/1,bin_to_binary/1]).
+-export([to_doc_info/1,to_doc_info_path/1]).
+-export([bin_foldl/3,bin_size/1,bin_to_binary/1,get_validate_doc_fun/1]).
 -export([from_json_obj/1,to_json_obj/2,has_stubs/1, merge_stubs/2]).
 
 -include("couch_db.hrl").
@@ -130,8 +130,11 @@
         attachments = Bins
         }.
 
+to_doc_info(FullDocInfo) ->
+    {DocInfo, _Path} = to_doc_info_path(FullDocInfo),
+    DocInfo.
 
-to_doc_info(#full_doc_info{id=Id,update_seq=Seq,rev_tree=Tree}) ->
+to_doc_info_path(#full_doc_info{id=Id,update_seq=Seq,rev_tree=Tree}) ->
     LeafRevs = couch_key_tree:get_all_leafs(Tree),
     SortedLeafRevs =
     lists:sort(fun({RevIdA, {IsDeletedA, _}, PathA}, {RevIdB, {IsDeletedB, _}, PathB}) ->
@@ -142,7 +145,7 @@
         end,
         LeafRevs),
 
-    [{RevId, {IsDeleted, SummaryPointer}, _Path} | Rest] = SortedLeafRevs,
+    [{RevId, {IsDeleted, SummaryPointer}, Path} | Rest] = SortedLeafRevs,
 
     {ConflictRevTuples, DeletedConflictRevTuples} =
         lists:splitwith(fun({_ConflictRevId, {IsDeleted1, _Sp}, _}) ->
@@ -151,22 +154,15 @@
 
     ConflictRevs = [RevId1  || {RevId1, _, _} <- ConflictRevTuples],
     DeletedConflictRevs = [RevId2   || {RevId2, _, _} <- DeletedConflictRevTuples],
-    #doc_info{
+    DocInfo = #doc_info{
         id=Id,
         update_seq=Seq,
         rev = RevId,
         summary_pointer = SummaryPointer,
         conflict_revs = ConflictRevs,
         deleted_conflict_revs = DeletedConflictRevs,
-        deleted = IsDeleted
-        }.
-
-is_special_doc(?DESIGN_DOC_PREFIX ++ _ ) ->
-    true;
-is_special_doc(#doc{id=Id}) ->
-    is_special_doc(Id);
-is_special_doc(_) ->
-    false.
+        deleted = IsDeleted},
+    {DocInfo, Path}.
 
 bin_foldl(Bin, Fun, Acc) when is_binary(Bin) ->
     case Fun(Bin, Acc) of
@@ -188,12 +184,18 @@
     {ok, Bin, _Sp2} = couch_stream:read(Fd, Sp, Len),
     Bin.
 
-get_view_functions(#doc{body={Fields}}) ->
-    Lang = proplists:get_value(<<"language">>, Fields, <<"javascript">>),
-    {Views} = proplists:get_value(<<"views">>, Fields, {[]}),
-    {Lang, [{ViewName, Value} || {ViewName, Value} <- Views, is_list(Value)]};
-get_view_functions(_Doc) ->
-    none.
+get_validate_doc_fun(#doc{body={Props}}) ->
+    Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+    case proplists:get_value(<<"validate_doc_update">>, Props) of
+    undefined ->
+        nil;
+    FunSrc ->
+        fun(EditDoc, DiskDoc, Ctx) ->
+            couch_query_servers:validate_doc_update(
+                    Lang, FunSrc, EditDoc, DiskDoc, Ctx)
+        end
+    end.
+        
 
 has_stubs(#doc{attachments=Bins}) ->
     has_stubs(Bins);

Modified: incubator/couchdb/trunk/src/couchdb/couch_erl_driver.c
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_erl_driver.c?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_erl_driver.c (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_erl_driver.c Tue Nov 11 11:45:50 2008
@@ -14,8 +14,8 @@
 */
 
 // This file is the C port driver for Erlang. It provides a low overhead
-// means of calling into C code, however unlike the Fabric engine, coding
-// errors in this module can crash the entire Erlang server.
+// means of calling into C code, however coding errors in this module can
+// crash the entire Erlang server.
 
 #ifdef DARWIN
 #define U_HIDE_DRAFT_API 1
@@ -56,10 +56,8 @@
         return ERL_DRV_ERROR_GENERAL;
 
     pData->port = port;
-    pData->coll = NULL;
-    pData->collNoCase = NULL;
+    
     pData->coll = ucol_open("", &status);
-
     if (U_FAILURE(status)) {
         couch_drv_stop((ErlDrvData)pData);
         return ERL_DRV_ERROR_GENERAL;

Modified: incubator/couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_httpd.erl Tue Nov 11 11:45:50 2008
@@ -16,7 +16,7 @@
 -export([start_link/0, stop/0, handle_request/3]).
 
 -export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1]).
--export([check_is_admin/1,unquote/1]).
+-export([check_is_admin/1,unquote/1,creds/1]).
 -export([parse_form/1,json_body/1,body/1,doc_etag/1]).
 -export([primary_header_value/2,partition/1,serve_file/3]).
 -export([start_chunked_response/3,send_chunk/2]).
@@ -197,14 +197,37 @@
 doc_etag(#doc{revs=[DiskRev|_]}) ->
      "\"" ++ binary_to_list(DiskRev) ++ "\"".
 
-check_is_admin(Req) ->
-    IsNamedAdmin =
+
+% user credentials
+creds(Req) ->
+    case username_pw(Req) of
+    {User, _Pw} ->
+        {[{<<"name">>, ?l2b(User)}]};
+    nil ->
+        {[]}
+    end.
+
+username_pw(Req) ->
     case header_value(Req, "Authorization") of
     "Basic " ++ Base64Value ->
-        [User, Pass] =
-            string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),":"),
-        couch_server:is_admin(User, Pass);
+        case string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),":") of
+        [User, Pass] ->
+            {User, Pass};
+        [User] ->
+            {User, <<"">>};
+        _ ->
+            nil
+        end;
     _ ->
+        nil
+    end.
+
+check_is_admin(Req) ->
+    IsNamedAdmin =
+    case username_pw(Req) of
+    {User, Pass} ->
+        couch_server:is_admin(User, Pass);
+    nil ->
         false
     end,
     
@@ -214,7 +237,7 @@
     false ->
         case couch_server:has_admins() of
         true ->
-            throw(admin_auth_error);
+            throw(admin_authorization_error);
         false ->
             % if no admins, then everyone is admin! Yay, admin party!
             ok
@@ -265,7 +288,6 @@
     send_chunk(Resp, []).
 
 
-
 send_error(Req, bad_request) ->
     send_error(Req, 400, <<"bad_request">>, <<>>);
 send_error(Req, {bad_request, Reason}) ->
@@ -276,13 +298,18 @@
     send_error(Req, 404, <<"not_found">>, Reason);
 send_error(Req, conflict) ->
     send_error(Req, 412, <<"conflict">>, <<"Document update conflict.">>);
-send_error(Req, admin_auth_error) ->
+send_error(Req, admin_authorization_error) ->
     send_json(Req, 401,
-        [{"WWW-Authenticate", "Basic realm=\"admin\""}],
-        {[{<<"error">>,  <<"auth_error">>},
+        [{"WWW-Authenticate", "Basic realm=\"administrator\""}],
+        {[{<<"error">>,  <<"authorization">>},
          {<<"reason">>, <<"Admin user name and password required">>}]});
-send_error(Req, {doc_validation, Msg}) ->
-    send_error(Req, 406, <<"doc_validation">>, Msg);
+send_error(Req, {user_error, {Props}}) ->
+    {Headers} = proplists:get_value(<<"headers">>, Props, {[]}),
+    send_json(Req,
+        proplists:get_value(<<"http_status">>, Props, 500),
+        Headers,
+        {[{<<"error">>, proplists:get_value(<<"error">>, Props)},
+            {<<"reason">>, proplists:get_value(<<"reason">>, Props)}]});
 send_error(Req, file_exists) ->
     send_error(Req, 409, <<"file_exists">>, <<"The database could not be "
         "created, the file already exists.">>);

Modified: incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl Tue Nov 11 11:45:50 2008
@@ -43,7 +43,7 @@
 
 create_db_req(Req, DbName) ->
     ok = couch_httpd:check_is_admin(Req),
-    case couch_server:create(DbName, []) of
+    case couch_server:create(DbName, [{creds, couch_httpd:creds(Req)}]) of
     {ok, Db} ->
         couch_db:close(Db),
         send_json(Req, 201, {[{ok, true}]});
@@ -53,7 +53,7 @@
 
 delete_db_req(Req, DbName) ->
     ok = couch_httpd:check_is_admin(Req),
-    case couch_server:delete(DbName) of
+    case couch_server:delete(DbName, [{creds, couch_httpd:creds(Req)}]) of
     ok ->
         send_json(Req, 200, {[{ok, true}]});
     Error ->
@@ -61,7 +61,7 @@
     end.
 
 do_db_req(#httpd{path_parts=[DbName|_]}=Req, Fun) ->
-    case couch_db:open(DbName, []) of
+    case couch_db:open(DbName, [{creds, couch_httpd:creds(Req)}]) of
     {ok, Db} ->
         try
             Fun(Req, Db)
@@ -129,7 +129,7 @@
             _ -> []
         end,
         Docs = [couch_doc:from_json_obj(JsonObj) || JsonObj <- DocsArray],
-        ok = couch_db:save_docs(Db, Docs, Options),
+        ok = couch_db:update_docs(Db, Docs, Options, false),
         send_json(Req, 201, {[
             {ok, true}
         ]})

Modified: incubator/couchdb/trunk/src/couchdb/couch_key_tree.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_key_tree.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_key_tree.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_key_tree.erl Tue Nov 11 11:45:50 2008
@@ -13,7 +13,7 @@
 -module(couch_key_tree).
 
 -export([merge/2, find_missing/2, get_key_leafs/2, get_full_key_paths/2, get/2]).
--export([map/2, get_all_leafs/1, get_leaf_keys/1, count_leafs/1, remove_leafs/2]).
+-export([map/2, get_all_leafs/1, get_leaf_keys/1, count_leafs/1, remove_leafs/2,get_all_leafs_full/1]).
 
 % a key tree looks like this:
 % Tree -> [] or [{Key, Value, ChildTree} | SiblingTree]
@@ -137,6 +137,16 @@
     {KeysGotten2, KeysRemaining2} = get_full_key_paths(RestTree, KeysRemaining, KeyPathAcc),
     {CurrentNodeResult ++ KeysGotten ++ KeysGotten2, KeysRemaining2}.
 
+get_all_leafs_full(Tree) ->
+    get_all_leafs_full(Tree, []).
+    
+get_all_leafs_full([], _KeyPathAcc) ->
+    [];
+get_all_leafs_full([{KeyId, Value, []} | RestTree], KeyPathAcc) ->
+    [[{KeyId, Value} | KeyPathAcc] | get_all_leafs_full(RestTree, KeyPathAcc)];
+get_all_leafs_full([{KeyId, Value, SubTree} | RestTree], KeyPathAcc) ->
+    get_all_leafs_full(SubTree, [{KeyId, Value} | KeyPathAcc]) ++ get_all_leafs_full(RestTree, KeyPathAcc).
+
 get_all_leafs(Tree) ->
     get_all_leafs(Tree, []).
     

Modified: incubator/couchdb/trunk/src/couchdb/couch_query_servers.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_query_servers.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_query_servers.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_query_servers.erl Tue Nov 11 11:45:50 2008
@@ -17,7 +17,7 @@
 
 -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]).
 -export([start_doc_map/2, map_docs/2, stop_doc_map/1]).
--export([reduce/3, rereduce/3]).
+-export([reduce/3, rereduce/3,validate_doc_update/5]).
 % -export([test/0]).
 
 -include("couch_db.hrl").
@@ -46,10 +46,10 @@
         lists:reverse(Acc, Data);
     {Port, Err} ->
         catch port_close(Port),
-        throw({map_process_error, Err})
+        throw({external_process_error, Err})
     after Timeout ->
         catch port_close(Port),
-        throw({map_process_error, "map function timed out"})
+        throw({external_process_error, "External process timed out"})
     end.
 
 read_json(Port) ->
@@ -174,6 +174,25 @@
     return_linked_port(Lang, Port),
     {ok, Results}.
 
+validate_doc_update(Lang, FunSrc, EditDoc, DiskDoc, Ctx) ->
+    Port = get_linked_port(Lang),
+    JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
+    JsonDiskDoc =
+    if DiskDoc == nil ->
+        null;
+    true -> 
+        couch_doc:to_json_obj(EditDoc, [revs])
+    end,
+    try prompt(Port, 
+            [<<"validate">>, FunSrc, JsonEditDoc, JsonDiskDoc, Ctx]) of
+    1 ->
+        ok;
+    {ErrorObject} ->
+        {user_error,
+           {ErrorObject}}
+    after
+        return_linked_port(Lang, Port)
+    end.
 
 init([]) ->
     

Modified: incubator/couchdb/trunk/src/couchdb/couch_rep.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_rep.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_rep.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_rep.erl Tue Nov 11 11:45:50 2008
@@ -209,7 +209,7 @@
     receive
     {Src, docs, Docs} ->
         Src ! got_it,
-        ok = save_docs(DbTarget, Docs, []),
+        ok = update_docs(DbTarget, Docs, [], false),
         save_docs_loop(DbTarget, DocsWritten + length(Docs));
     {Src, shutdown} ->
         Src ! {done, self(), [{<<"docs_written">>, DocsWritten}]}
@@ -313,16 +313,16 @@
 update_doc(Db, Doc, Options) ->
     couch_db:update_doc(Db, Doc, Options).
 
-save_docs(_, [], _) ->
+update_docs(_, [], _, _) ->
     ok;
-save_docs(DbUrl, Docs, []) when is_list(DbUrl) ->
+update_docs(DbUrl, Docs, [], NewEdits) when is_list(DbUrl) ->
     JsonDocs = [couch_doc:to_json_obj(Doc, [revs,attachments]) || Doc <- Docs],
     {Returned} =
-        do_http_request(DbUrl ++ "_bulk_docs", post, {[{new_edits, false}, {docs, JsonDocs}]}),
+        do_http_request(DbUrl ++ "_bulk_docs", post, {[{new_edits, NewEdits}, {docs, JsonDocs}]}),
     true = proplists:get_value(<<"ok">>, Returned),
     ok;
-save_docs(Db, Docs, Options) ->
-    couch_db:save_docs(Db, Docs, Options).
+update_docs(Db, Docs, Options, NewEdits) ->
+    couch_db:update_docs(Db, Docs, Options, NewEdits).
 
 
 open_doc(DbUrl, DocId, []) when is_list(DbUrl) ->

Modified: incubator/couchdb/trunk/src/couchdb/couch_server.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_server.erl?rev=713132&r1=713131&r2=713132&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_server.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_server.erl Tue Nov 11 11:45:50 2008
@@ -15,7 +15,7 @@
 -behaviour(application).
 
 -export([start/0,start/1,start/2,stop/0,stop/1,restart/0]).
--export([open/2,create/2,delete/1,all_databases/0,get_version/0]).
+-export([open/2,create/2,delete/2,all_databases/0,get_version/0]).
 -export([init/1, handle_call/3,sup_start_link/0]).
 -export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
 -export([dev_start/0,remote_restart/0,is_admin/2,has_admins/0]).
@@ -71,8 +71,8 @@
 create(DbName, Options) ->
     gen_server:call(couch_server, {create, DbName, Options}).
 
-delete(DbName) ->
-    gen_server:call(couch_server, {delete, DbName}).
+delete(DbName, Options) ->
+    gen_server:call(couch_server, {delete, DbName, Options}).
 
 remote_restart() ->
     gen_server:call(couch_server, remote_restart).
@@ -202,6 +202,7 @@
     {reply, {ok, Root}, Server};
 handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
     DbNameList = binary_to_list(DbName),
+    UserCreds = proplists:get_value(creds, Options, nil),
     case check_dbname(Server, DbNameList) of
     ok ->
         Filepath = get_full_filename(Server, DbNameList),
@@ -217,7 +218,7 @@
                     true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
                     DbsOpen = Server2#server.current_dbs_open + 1,
                     {reply,
-                        couch_db:open_ref_counted(MainPid, FromPid),
+                        couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
                         Server2#server{current_dbs_open=DbsOpen}};
                 Error ->
                     {reply, Error, Server2}
@@ -229,13 +230,16 @@
             true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, LruTime}}),
             true = ets:delete(couch_dbs_by_lru, PrevLruTime),
             true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
-            {reply, couch_db:open_ref_counted(MainPid, FromPid), Server}
+            {reply,
+                couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
+                Server}
         end;
     Error ->
         {reply, Error, Server}
     end;
 handle_call({create, DbName, Options}, {FromPid,_}, Server) ->
     DbNameList = binary_to_list(DbName),
+    UserCreds = proplists:get_value(creds, Options, nil),
     case check_dbname(Server, DbNameList) of
     ok ->
         Filepath = get_full_filename(Server, DbNameList),
@@ -251,7 +255,7 @@
                 DbsOpen = Server#server.current_dbs_open + 1,
                 couch_db_update_notifier:notify({created, DbName}),
                 {reply,
-                    couch_db:open_ref_counted(MainPid, FromPid), 
+                    couch_db:open_ref_counted(MainPid, FromPid, UserCreds), 
                     Server#server{current_dbs_open=DbsOpen}};
             Error ->
                 {reply, Error, Server}
@@ -262,8 +266,9 @@
     Error ->
         {reply, Error, Server}
     end;
-handle_call({delete, DbName}, _From, Server) ->
+handle_call({delete, DbName, Options}, _From, Server) ->
     DbNameList = binary_to_list(DbName),
+    _UserCreds = proplists:get_value(creds, Options, nil),
     case check_dbname(Server, DbNameList) of
     ok ->
         FullFilepath = get_full_filename(Server, DbNameList),
@@ -306,10 +311,10 @@
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
-handle_info({'EXIT', Pid, _Reason}, Server) ->
+handle_info({'EXIT', Pid, _Reason}, #server{current_dbs_open=DbsOpen}=Server) ->
     [{Pid, DbName}] = ets:lookup(couch_dbs_by_pid, Pid),
     true = ets:delete(couch_dbs_by_pid, Pid),
     true = ets:delete(couch_dbs_by_name, DbName),
-    {noreply, Server};
+    {noreply, Server#server{current_dbs_open=DbsOpen-1}};
 handle_info(Info, _Server) ->
     exit({unknown_message, Info}).