You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2008/08/16 23:01:22 UTC

svn commit: r686560 - in /incubator/couchdb/branches/runtimeconfig: ./ CHANGES Makefile.am README configure.ac share/www/script/couch_tests.js src/couchdb/couch_btree.erl src/couchdb/couch_db.hrl src/couchdb/couch_view.erl

Author: jan
Date: Sat Aug 16 14:01:18 2008
New Revision: 686560

URL: http://svn.apache.org/viewvc?rev=686560&view=rev
Log:
Merged revisions 685963,685968,685975,686007,686011,686014,686383-686385 via svnmerge from 
https://svn.apache.org/repos/asf/incubator/couchdb/trunk


Modified:
    incubator/couchdb/branches/runtimeconfig/   (props changed)
    incubator/couchdb/branches/runtimeconfig/CHANGES
    incubator/couchdb/branches/runtimeconfig/Makefile.am
    incubator/couchdb/branches/runtimeconfig/README
    incubator/couchdb/branches/runtimeconfig/configure.ac
    incubator/couchdb/branches/runtimeconfig/share/www/script/couch_tests.js
    incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_btree.erl
    incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_db.hrl
    incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_view.erl

Propchange: incubator/couchdb/branches/runtimeconfig/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Sat Aug 16 14:01:18 2008
@@ -1 +1 @@
-/incubator/couchdb/trunk:1-685172
+/incubator/couchdb/trunk:1-686559

Modified: incubator/couchdb/branches/runtimeconfig/CHANGES
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/CHANGES?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/CHANGES (original)
+++ incubator/couchdb/branches/runtimeconfig/CHANGES Sat Aug 16 14:01:18 2008
@@ -1,6 +1,14 @@
 Apache CouchDB CHANGES
 ======================
 
+Version 0.9.0-incubating
+------------------------
+
+Packaging and System Integration:
+
+ * Manually linked `libm` for portability with FreeBSD 7.0.
+ * Extended default library paths in `configure.ac` file.
+
 Version 0.8.1-incubating
 ------------------------
 
@@ -31,13 +39,12 @@
    SpiderMonkey 1.9.
  * Improve error handling for undefined values emitted by map functions.
    (COUCHDB-83)
- 
 
 Packaging and System Integration:
 
  * The `couchdb` script no longer uses `awk` for configuration checks as this
    was causing portability problems.
- * Updated `sudo` example in the README to use the `-i` option, this fixes
+ * Updated `sudo` example in the `README` to use the `-i` option, this fixes
    problems when invoking from a directory the `couchdb` user cannot access.
 
 Futon Utility Client:
@@ -57,53 +64,53 @@
    and old document revisions.
  * Support for incremental map/reduce views has been added.
  * To support map/reduce, the structure of design documents has changed. View
-   values are now JSON objects containing at least a "map" member, and
-   optionally a "reduce" member.
- * View servers are now identified by name (for example "javascript") instead of
-   by MIME type.
+   values are now JSON objects containing at least a `map` member, and
+   optionally a `reduce` member.
+ * View servers are now identified by name (for example `javascript`) instead of
+   by media type.
  * Automatically generated document IDs are now based on proper UUID generation
    using the crypto module.
- * The field "content-type" in the JSON representation of attachments has been
-   renamed to "content_type" (underscore).
+ * The field `content-type` in the JSON representation of attachments has been
+   renamed to `content_type` (underscore).
 
 HTTP Interface:
 
  * CouchDB now uses MochiWeb instead of inets for the HTTP server
    implementation. Among other things, this means that the extra configuration
-   files needed for inets (such as couch_httpd.conf) are no longer used.
- * The HTTP interface now completely supports the HEAD method. (COUCHDB-3)
- * Improved compliance of Etag handling with the HTTP specification.
+   files needed for inets (such as `couch_httpd.conf`) are no longer used.
+ * The HTTP interface now completely supports the `HEAD` method. (COUCHDB-3)
+ * Improved compliance of `Etag` handling with the HTTP specification.
    (COUCHDB-13)
- * Etags are no longer included in responses to document GET requests that
+ * Etags are no longer included in responses to document `GET` requests that
    include query string parameters causing the JSON response to change without
    the revision or the URI having changed.
  * The bulk document update API has changed slightly on both the request and the
    response side. In addition, bulk updates are now atomic.
- * CouchDB now uses TCP_NODELAY to fix performance problems with persistent
+ * CouchDB now uses `TCP_NODELAY` to fix performance problems with persistent
    connections on some platforms due to nagling.
  * Including a `?descending=false` query string parameter in requests to views
    no longer raises an error.
  * Requests to unknown top-level reserved URLs (anything with a leading
-   underscore) now return a "unknown_private_path" error instead of the
-   confusing "illegal_database_name".
+   underscore) now return a `unknown_private_path` error instead of the
+   confusing `illegal_database_name`.
  * The Temporary view handling now expects a JSON request body, where the JSON
-   is an object with at least a "map" member, and optional "reduce" and
-   "language" members.
+   is an object with at least a `map` member, and optional `reduce` and
+   `language` members.
  * Temporary views no longer determine the view server based on the Content-Type
-   header of the POST request, but rather by looking for a "language" member in
-   the JSON body of the request.
- * The status code of responses to DELETE requests is now 200 to reflect that
+   header of the `POST` request, but rather by looking for a `language` member
+   in the JSON body of the request.
+ * The status code of responses to `DELETE` requests is now 200 to reflect that
    that the deletion is performed synchronously.
 
 Javascript View Server:
 
  * SpiderMonkey is no longer included with CouchDB, but rather treated as a
-   normal external dependency. A simple C program (_couchjs) is provided that
+   normal external dependency. A simple C program (`_couchjs`) is provided that
    links against an existing SpiderMonkey installation and uses the interpreter
    embedding API.
  * View functions using the default Javascript view server can now do logging
    using the global `log(message)` function. Log messages are directed into the
-   CouchDB log at INFO level. (COUCHDB-59)
+   CouchDB log at `INFO` level. (COUCHDB-59)
  * The global `map(key, value)` function made available to view code has been
    renamed to `emit(key, value)`.
  * Fixed handling of exceptions raised by view functions.
@@ -130,10 +137,10 @@
  * Futon now uses the XMLHTTPRequest API asynchronously to communicate with the
    CouchDB HTTP server, so that most operations no longer block the browser.
  * View results sorting can now be switched between ascending and descending by
-   clicking on the "Key" column header.
- * Fixed a bug where documents that contained a "@" character could not be
+   clicking on the `Key` column header.
+ * Fixed a bug where documents that contained a `@` character could not be
    viewed. (COUCHDB-12)
- * The database page now provides a "Compact" button to trigger database
+ * The database page now provides a `Compact` button to trigger database
    compaction. (COUCHDB-38)
  * Fixed portential double encoding of document IDs and other URI segments in
    many instances. (COUCHDB-39)

Modified: incubator/couchdb/branches/runtimeconfig/Makefile.am
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/Makefile.am?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/Makefile.am (original)
+++ incubator/couchdb/branches/runtimeconfig/Makefile.am Sat Aug 16 14:01:18 2008
@@ -36,18 +36,28 @@
 THANKS.gz: $(top_srcdir)/THANKS
 	-gzip -9 < $< > $@
 
+<<<<<<< .working
 bootstrap-clean: maintainer-clean
-	@echo "This command is intended for maintainers to use;"
-	@echo "it deletes files that may need special tools to rebuild."
-	rm -f INSTALL
-	rm -f acinclude.m4
-	rm -f aclocal.m4
-	rm -fr build-aux
-	rm -f config.h.in*
-	rm -f configure
-	rm -f configure~
-	rm -f *.tar.gz
-	rm -fr m4
-	find . -name Makefile.in | xargs rm
-
+=======
 .PHONY: local-clean
+local-clean: maintainer-clean
+        @echo "This command is intended for maintainers to use;"
+        @echo "it deletes files that may need special tools to rebuild."
+        -rm -f INSTALL
+        -rm -f acinclude.m4
+        -rm -f aclocal.m4
+        -rm -f config.h.in
+        -rm -f configure
+        -rm -f *.tar.gz
+        -rm -f *.tar.gz.asc
+        -rm -f *.tar.gz.md5
+        -rm -f *.tar.gz.sha
+        -rm -fr build-aux
+        -rm -fr m4
+        -find $(top_srcdir) -name Makefile.in | xargs rm
+
+.PHONY: distsign
+distsign: distcheck
+	gpg --armor --detach-sig < $(distdir).tar.gz > $(distdir).tar.gz.asc
+	md5sum $(distdir).tar.gz > $(distdir).tar.gz.md5
+	sha1sum $(distdir).tar.gz > $(distdir).tar.gz.sha

Modified: incubator/couchdb/branches/runtimeconfig/README
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/README?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/README (original)
+++ incubator/couchdb/branches/runtimeconfig/README Sat Aug 16 14:01:18 2008
@@ -124,6 +124,9 @@
 system owned directory. You do not need to do this if you are installing
 elsewhere, such as your home directory.
 
+If you are having problems running `make` you may want to try running `gmake` if
+it is available on your system.
+
 More options can be found by reading the `INSTALL` file.
 
 #### Security Considerations ####

Modified: incubator/couchdb/branches/runtimeconfig/configure.ac
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/configure.ac?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/configure.ac (original)
+++ incubator/couchdb/branches/runtimeconfig/configure.ac Sat Aug 16 14:01:18 2008
@@ -59,24 +59,28 @@
 
 AC_ARG_WITH([js-include], [AC_HELP_STRING([--with-js-include=PATH],
     [set PATH to the SpiderMonkey include directory])], [
-    JS_INCLUDE_FLAGS="-I$withval"
+    JS_FLAGS="-I$withval"
 ], [
-    JS_INCLUDE_FLAGS="-I/usr/include/js"
-    JS_INCLUDE_FLAGS="$JS_INCLUDE_FLAGS -I/usr/include/mozjs"
-    JS_INCLUDE_FLAGS="$JS_INCLUDE_FLAGS -I/usr/local/include/js"
-    JS_INCLUDE_FLAGS="$JS_INCLUDE_FLAGS -I/opt/local/include/js"
+    JS_FLAGS="-I/usr/include"
+    JS_FLAGS="$JS_FLAGS -I/usr/include/js"
+    JS_FLAGS="$JS_FLAGS -I/usr/include/mozjs"
+    JS_FLAGS="$JS_FLAGS -I/usr/local/include"
+    JS_FLAGS="$JS_FLAGS -I/opt/local/include"
+    JS_FLAGS="$JS_FLAGS -I/usr/local/include/js"
+    JS_FLAGS="$JS_FLAGS -I/opt/local/include/js"
 ])
 
 AC_ARG_WITH([js-lib], [AC_HELP_STRING([--with-js-lib=PATH],
     [set PATH to the SpiderMonkey library directory])],
     [JS_LIB_FLAGS="-L$withval"], [])
 
-# XP_UNIX required for jsapi.h and has been tested to work on Linux and Darwin.
 LIB_FLAGS="-L/usr/local/lib -L/opt/local/lib $JS_LIB_FLAGS"
 LIBS="$LIB_FLAGS $LIBS"
-FLAGS="$LIB_FLAGS $ERLANG_FLAGS $JS_INCLUDE_FLAGS -DXP_UNIX $FLAGS"
+# XP_UNIX required for jsapi.h and has been tested to work on Linux and Darwin.
+FLAGS="$LIB_FLAGS $ERLANG_FLAGS $JS_FLAGS -DXP_UNIX $FLAGS"
 CPPFLAGS="$FLAGS $CPPFLAGS"
-LDFLAGS="$FLAGS $LDFLAGS"
+# manually linking libm is requred for FreeBSD 7.0
+LDFLAGS="$FLAGS -lm $LDFLAGS"
 
 AC_CHECK_LIB([js], [JS_NewContext], [], [
     AC_CHECK_LIB([mozjs], [JS_NewContext], [], [
@@ -87,7 +91,7 @@
 AC_CHECK_HEADER([jsapi.h], [], [
     AC_MSG_ERROR([Could not find the jsapi header.
 
-Are Mozilla SpiderMonkey headers installed?])])
+Are the Mozilla SpiderMonkey headers installed?])])
 
 AC_CHECK_ICU([3])
 

Modified: incubator/couchdb/branches/runtimeconfig/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/share/www/script/couch_tests.js?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/share/www/script/couch_tests.js [utf-8] (original)
+++ incubator/couchdb/branches/runtimeconfig/share/www/script/couch_tests.js [utf-8] Sat Aug 16 14:01:18 2008
@@ -363,29 +363,30 @@
     T(db.bulkSave(docs).ok);
     var summate = function(N) {return (N+1)*N/2;};
 
-    var map = function (doc) {emit(doc.integer, doc.integer)};
+    var map = function (doc) {
+        emit(doc.integer, doc.integer);
+        emit(doc.integer, doc.integer)};
     var reduce = function (keys, values) { return sum(values); };
     var result = db.query(map, reduce);
-    T(result.rows[0].value == summate(numDocs));
+    T(result.rows[0].value == 2*summate(numDocs));
 
     result = db.query(map, reduce, {startkey: 4, endkey: 4});
-    T(result.rows[0].value == 4);
+    T(result.rows[0].value == 8);
 
     result = db.query(map, reduce, {startkey: 4, endkey: 5});
-    T(result.rows[0].value == 9);
+    T(result.rows[0].value == 18);
 
     result = db.query(map, reduce, {startkey: 4, endkey: 6});
-    T(result.rows[0].value == 15);
+    T(result.rows[0].value == 30);
 
     result = db.query(map, reduce, {group:true, count:3});
-    T(result.rows.length == 3);
-    T(result.rows[0].value == 1);
-    T(result.rows[1].value == 2);
-    T(result.rows[2].value == 3);
+    T(result.rows[0].value == 2);
+    T(result.rows[1].value == 4);
+    T(result.rows[2].value == 6);
 
     for(var i=1; i<numDocs/2; i+=30) {
       result = db.query(map, reduce, {startkey: i, endkey: numDocs - i});
-      T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
+      T(result.rows[0].value == 2*(summate(numDocs-i) - summate(i-1)));
     }
 
     db.deleteDb();
@@ -799,7 +800,7 @@
       _id:"_design/test",
       language: "javascript",
       views: {
-        all_docs: {map: "function(doc) { emit(doc.integer, null) }"},
+        all_docs: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
         no_docs: {map: "function(doc) {}"},
         single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
         summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
@@ -815,9 +816,11 @@
     T(db.bulkSave(makeDocs(1, numDocs + 1)).ok);
 
     for (var loop = 0; loop < 2; loop++) {
+      if (db.view("test/all_docs") == null) throw "fuck";
       var rows = db.view("test/all_docs").rows;
-      for (var i = 1; i <= numDocs; i++) {
-        T(rows[i-1].key == i);
+      for (var i = 0; i < numDocs; i++) {
+        T(rows[2*i].key == i+1);
+        T(rows[(2*i)+1].key == i+1);
       }
       T(db.view("test/no_docs").total_rows == 0)
       T(db.view("test/single_doc").total_rows == 1)

Modified: incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_btree.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_btree.erl?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_btree.erl (original)
+++ incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_btree.erl Sat Aug 16 14:01:18 2008
@@ -14,7 +14,8 @@
 
 -export([open/2, open/3, query_modify/4, add/2, add_remove/3, foldl/3, foldl/4]).
 -export([foldr/3, foldr/4, fold/4, fold/5, full_reduce/1, final_reduce/2]).
--export([fold_reduce/7, lookup/2, get_state/1, set_options/2, test/1, test/0]).
+-export([fold_reduce/7, lookup/2, get_state/1, set_options/2]).
+-export([test/1, test/0, test_remove/2, test_add/2]).
 
 -define(CHUNK_THRESHOLD, 16#4ff).
 
@@ -180,49 +181,52 @@
     {NodeType, NodeList} = get_node(Bt, Pointer),
     case NodeType of
     kp_node ->
-        lookup_kpnode(Bt, NodeList, Keys, []);
+        lookup_kpnode(Bt, list_to_tuple(NodeList), 1, Keys, []);
     kv_node ->
-        lookup_kvnode(Bt, NodeList, Keys, [])
+        lookup_kvnode(Bt, list_to_tuple(NodeList), 1, Keys, [])
     end.
 
 
-lookup_kpnode(_Bt, [], Keys, Output) ->
-    {ok, lists:reverse(Output, [{Key, not_found} || Key <- Keys])};
 
-lookup_kpnode(_Bt, _KPs, [], Output) ->
+lookup_kpnode(_Bt, _NodeTuple, _LowerBound, [], Output) ->
     {ok, lists:reverse(Output)};
+    
+lookup_kpnode(_Bt, NodeTuple, LowerBound, Keys, Output) when size(NodeTuple) < LowerBound ->
+    {ok, lists:reverse(Output, [{Key, not_found} || Key <- Keys])};
 
-lookup_kpnode(Bt, [{Key, PointerInfo} | RestKPs], LookupKeys, Output) ->
-    % Split the Keys into two lists, queries of values less
-    % than equals, and greater than the current key
+lookup_kpnode(Bt, NodeTuple, LowerBound, [FirstLookupKey | _] = LookupKeys, Output) ->
+    N = find_first_gteq(Bt, NodeTuple, LowerBound, size(NodeTuple), FirstLookupKey),
+    {Key, PointerInfo} = element(N, NodeTuple),
     SplitFun = fun(LookupKey) -> not less(Bt, Key, LookupKey) end,
     case lists:splitwith(SplitFun, LookupKeys) of
     {[], GreaterQueries} ->
-        lookup_kpnode(Bt, RestKPs, GreaterQueries, Output);
+        lookup_kpnode(Bt, NodeTuple, N + 1, GreaterQueries, Output);
     {LessEqQueries, GreaterQueries} ->
         {ok, Results} = lookup(Bt, PointerInfo, LessEqQueries),
-        lookup_kpnode(Bt, RestKPs, GreaterQueries, lists:reverse(Results, Output))
+        lookup_kpnode(Bt, NodeTuple, N + 1, GreaterQueries, lists:reverse(Results, Output))
     end.
 
 
-
-lookup_kvnode(_Bt, _KVs, [], Output) ->
+lookup_kvnode(_Bt, _NodeTuple, _LowerBound, [], Output) ->
     {ok, lists:reverse(Output)};
-lookup_kvnode(_Bt, [], Keys, Output) ->
+lookup_kvnode(_Bt, NodeTuple, LowerBound, Keys, Output) when size(NodeTuple) < LowerBound ->
     % keys not found
     {ok, lists:reverse(Output, [{Key, not_found} || Key <- Keys])};
-lookup_kvnode(Bt, [{Key, Value} | RestKVs], [LookupKey | RestLookupKeys], Output) ->
+lookup_kvnode(Bt, NodeTuple, LowerBound, [LookupKey | RestLookupKeys], Output) ->
+    N = find_first_gteq(Bt, NodeTuple, LowerBound, size(NodeTuple), LookupKey),
+    {Key, Value} = element(N, NodeTuple),
     case less(Bt, LookupKey, Key) of
     true ->
-        lookup_kvnode(Bt, [{Key, Value} | RestKVs], RestLookupKeys, [{LookupKey, not_found} | Output]);
+        % LookupKey is less than Key
+        lookup_kvnode(Bt, NodeTuple, N, RestLookupKeys, [{LookupKey, not_found} | Output]);
     false ->
         case less(Bt, Key, LookupKey) of
         true ->
             % LookupKey is greater than Key
-            lookup_kvnode(Bt, RestKVs, [LookupKey | RestLookupKeys], Output);
+            lookup_kvnode(Bt, NodeTuple, N+1, RestLookupKeys, [{LookupKey, not_found} | Output]);
         false ->
             % LookupKey is equal to Key
-            lookup_kvnode(Bt, RestKVs, RestLookupKeys, [{LookupKey, {ok, assemble(Bt, LookupKey, Value)}} | Output])
+            lookup_kvnode(Bt, NodeTuple, N, RestLookupKeys, [{LookupKey, {ok, assemble(Bt, LookupKey, Value)}} | Output])
         end
     end.
 
@@ -272,17 +276,18 @@
     {Pointer, _Reds} ->
         {NodeType, NodeList} = get_node(Bt, Pointer)
     end,
+    NodeTuple = list_to_tuple(NodeList),
     case NodeType of
     kp_node ->
-        {ok, NewNodeList, QueryOutput2, Bt2} = modify_kpnode(Bt, NodeList, Actions, [], QueryOutput);
+        {ok, NewNodeList, QueryOutput2, Bt2} = modify_kpnode(Bt, NodeTuple, 1, Actions, [], QueryOutput);
     kv_node ->
-        {ok, NewNodeList, QueryOutput2, Bt2} = modify_kvnode(Bt, NodeList, Actions, [], QueryOutput)
+        {ok, NewNodeList, QueryOutput2, Bt2} = modify_kvnode(Bt, NodeTuple, 1, Actions, [], QueryOutput)
     end,
     case NewNodeList of
     [] ->  % no nodes remain
         {ok, [], QueryOutput2, Bt2};
     NodeList ->  % nothing changed
-        {LastKey, _LastValue} = lists:last(NodeList),
+        {LastKey, _LastValue} = element(size(NodeTuple), NodeTuple),
         {ok, [{LastKey, RootPointerInfo}], QueryOutput2, Bt2};
     _Else2 ->
         {ok, ResultList, Bt3} = write_node(Bt2, NodeType, NewNodeList),
@@ -299,17 +304,6 @@
 
 get_node(#btree{fd = Fd}, NodePos) ->
     {ok, {NodeType, NodeList}} = couch_file:pread_term(Fd, NodePos),
-    case NodeType of
-    kp_node ->
-        % Node pointers always point backward on disk.
-        % Validating this prevents infinite loops should
-        % a disk corruption occur.
-        [throw({error, disk_corruption})
-            || {_Key, {SubNodePos, _Reds}}
-            <- NodeList, SubNodePos >= NodePos];
-    kv_node ->
-        ok
-    end,
     {NodeType, NodeList}.
 
 write_node(Bt, NodeType, NodeList) ->
@@ -326,77 +320,108 @@
         ANodeList <- NodeListList
     ],
     {ok, ResultList, Bt}.
-
-
-modify_kpnode(Bt, KPs, [], ResultNode, QueryOutput) ->
-    % processed all queries for the current tree
-    {ok, lists:reverse(ResultNode, KPs), QueryOutput, Bt};
-
-modify_kpnode(Bt, [], Actions, [], QueryOutput) ->
+    
+modify_kpnode(Bt, {}, _LowerBound, Actions, [], QueryOutput) ->
     modify_node(Bt, nil, Actions, QueryOutput);
-
-modify_kpnode(Bt, [], Actions, [{_Key, PointerInfo} | ResultNode], QueryOutput) ->
-    {ok, ChildKPs, QueryOutput2, Bt2} = modify_node(Bt, PointerInfo, Actions, QueryOutput),
-    {ok, lists:reverse(ResultNode, ChildKPs), QueryOutput2, Bt2};
-
-modify_kpnode(Bt, [{Key,PointerInfo} | RestKPs], Actions, ResultNode, QueryOutput) ->
-    % Split the actions into two lists, queries of values <= and > than the current key
-    SplitFun = fun({_ActionType, ActionKey, _ActionValue}) ->
-            not less(Bt, Key, ActionKey)
-        end,
-    case lists:splitwith(SplitFun, Actions) of
-    {[], GreaterQueries} ->
-        modify_kpnode(Bt, RestKPs, GreaterQueries, [{Key, PointerInfo} | ResultNode], QueryOutput);
-    {LessEqQueries, GreaterQueries} ->
-        {ok, ChildKPs, QueryOutput2, Bt2} = modify_node(Bt, PointerInfo, LessEqQueries, QueryOutput),
-        modify_kpnode(Bt2, RestKPs, GreaterQueries, lists:reverse(ChildKPs, ResultNode), QueryOutput2)
+modify_kpnode(Bt, NodeTuple, LowerBound, [], ResultNode, QueryOutput) ->
+    {ok, lists:reverse(ResultNode, bounded_tuple_to_list(NodeTuple, LowerBound,
+            size(NodeTuple),  [])), QueryOutput, Bt};
+modify_kpnode(Bt, NodeTuple, LowerBound,
+        [{_, FirstActionKey, _}|_]=Actions, ResultNode, QueryOutput) ->
+    N = find_first_gteq(Bt, NodeTuple, LowerBound, size(NodeTuple), FirstActionKey),
+    case N == size(NodeTuple) of
+    true  ->
+        % perform remaining actions on last node
+        {_, PointerInfo} = element(size(NodeTuple), NodeTuple),
+        {ok, ChildKPs, QueryOutput2, Bt2} =
+            modify_node(Bt, PointerInfo, Actions, QueryOutput),
+        NodeList = lists:reverse(ResultNode, bounded_tuple_to_list(NodeTuple, LowerBound,
+            size(NodeTuple) - 1, ChildKPs)),
+        {ok, NodeList, QueryOutput2, Bt2};
+    false ->
+        {NodeKey, PointerInfo} = element(N, NodeTuple),
+        SplitFun = fun({_ActionType, ActionKey, _ActionValue}) ->
+                not less(Bt, NodeKey, ActionKey)
+            end,
+        {LessEqQueries, GreaterQueries} = lists:splitwith(SplitFun, Actions),
+        {ok, ChildKPs, QueryOutput2, Bt2} =
+                modify_node(Bt, PointerInfo, LessEqQueries, QueryOutput),
+        ResultNode2 = lists:reverse(ChildKPs, bounded_tuple_to_revlist(NodeTuple,
+                LowerBound, N - 1, ResultNode)),
+        modify_kpnode(Bt2, NodeTuple, N+1, GreaterQueries, ResultNode2, QueryOutput2)
+    end.
+    
+bounded_tuple_to_revlist(_Tuple, Start, End, Tail) when Start > End ->
+    Tail;
+bounded_tuple_to_revlist(Tuple, Start, End, Tail) ->
+    bounded_tuple_to_revlist(Tuple, Start+1, End, [element(Start, Tuple)|Tail]).
+        
+bounded_tuple_to_list(Tuple, Start, End, Tail) ->
+    bounded_tuple_to_list2(Tuple, Start, End, [], Tail).
+    
+bounded_tuple_to_list2(_Tuple, Start, End, Acc, Tail) when Start > End ->
+    lists:reverse(Acc, Tail);
+bounded_tuple_to_list2(Tuple, Start, End, Acc, Tail) ->
+    bounded_tuple_to_list2(Tuple, Start + 1, End, [element(Start, Tuple) | Acc], Tail).
+
+find_first_gteq(_Bt, _Tuple, Start, End, _Key) when Start == End ->
+    End;
+find_first_gteq(Bt, Tuple, Start, End, Key) ->
+    Mid = Start + ((End - Start) div 2),
+    {TupleKey, _} = element(Mid, Tuple),
+    case less(Bt, TupleKey, Key) of
+    true ->
+        find_first_gteq(Bt, Tuple, Mid+1, End, Key);
+    false ->
+        find_first_gteq(Bt, Tuple, Start, Mid, Key)
     end.
 
-modify_kvnode(Bt, KVs, [], ResultNode, QueryOutput) ->
-    {ok, lists:reverse(ResultNode, KVs), QueryOutput, Bt};
-modify_kvnode(Bt, [], [{ActionType, ActionKey, ActionValue} | RestActions], ResultNode, QueryOutput) ->
+modify_kvnode(Bt, NodeTuple, LowerBound, [], ResultNode, QueryOutput) ->
+    {ok, lists:reverse(ResultNode, bounded_tuple_to_list(NodeTuple, LowerBound, size(NodeTuple), [])), QueryOutput, Bt};
+modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} | RestActions], ResultNode, QueryOutput) when LowerBound > size(NodeTuple) ->
     case ActionType of
     insert ->
-        modify_kvnode(Bt, [], RestActions, [{ActionKey, ActionValue} | ResultNode], QueryOutput);
+        modify_kvnode(Bt, NodeTuple, LowerBound, RestActions, [{ActionKey, ActionValue} | ResultNode], QueryOutput);
     remove ->
         % just drop the action
-        modify_kvnode(Bt, [], RestActions, ResultNode, QueryOutput);
+        modify_kvnode(Bt, NodeTuple, LowerBound, RestActions, ResultNode, QueryOutput);
     fetch ->
         % the key/value must not exist in the tree
-        modify_kvnode(Bt, [], RestActions, ResultNode, [{not_found, {ActionKey, nil}} | QueryOutput])
+        modify_kvnode(Bt, NodeTuple, LowerBound, RestActions, ResultNode, [{not_found, {ActionKey, nil}} | QueryOutput])
     end;
-modify_kvnode(Bt, [{Key, Value} | RestKVs], [{ActionType, ActionKey, ActionValue} | RestActions], ResultNode, QueryOutput) ->
+modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} | RestActions], AccNode, QueryOutput) ->
+    N = find_first_gteq(Bt, NodeTuple, LowerBound, size(NodeTuple), ActionKey),
+    {Key, Value} = element(N, NodeTuple),
+    ResultNode =  bounded_tuple_to_revlist(NodeTuple, LowerBound, N - 1, AccNode),
     case less(Bt, ActionKey, Key) of
     true ->
         case ActionType of
         insert ->
             % ActionKey is less than the Key, so insert
-            modify_kvnode(Bt, [{Key, Value} | RestKVs], RestActions, [{ActionKey, ActionValue} | ResultNode], QueryOutput);
+            modify_kvnode(Bt, NodeTuple, N, RestActions, [{ActionKey, ActionValue} | ResultNode], QueryOutput);
         remove ->
             % ActionKey is less than the Key, just drop the action
-            modify_kvnode(Bt, [{Key, Value} | RestKVs], RestActions, ResultNode, QueryOutput);
+            modify_kvnode(Bt, NodeTuple, N, RestActions, ResultNode, QueryOutput);
         fetch ->
             % ActionKey is less than the Key, the key/value must not exist in the tree
-            modify_kvnode(Bt, [{Key, Value} | RestKVs], RestActions, ResultNode, [{not_found, {ActionKey, nil}} | QueryOutput])
+            modify_kvnode(Bt, NodeTuple, N, RestActions, ResultNode, [{not_found, {ActionKey, nil}} | QueryOutput])
         end;
     false ->
+        % ActionKey and Key are maybe equal.
         case less(Bt, Key, ActionKey) of
-        true ->
-            % ActionKey is greater than Key
-            modify_kvnode(Bt, RestKVs, [{ActionType, ActionKey, ActionValue} | RestActions], [{Key, Value} | ResultNode], QueryOutput);
         false ->
-            % InsertKey is equal to Key
             case ActionType of
             insert ->
-                % ActionKey is less than the Key, so insert
-                modify_kvnode(Bt, RestKVs, RestActions, [{ActionKey, ActionValue} | ResultNode], QueryOutput);
+                modify_kvnode(Bt, NodeTuple, N+1, RestActions, [{ActionKey, ActionValue} | ResultNode], QueryOutput);
             remove ->
-                modify_kvnode(Bt, RestKVs, RestActions, ResultNode, QueryOutput);
+                modify_kvnode(Bt, NodeTuple, N+1, RestActions, ResultNode, QueryOutput);
             fetch ->
                 % ActionKey is equal to the Key, insert into the QueryOuput, but re-process the node
                 % since an identical action key can follow it.
-                modify_kvnode(Bt, [{Key, Value} | RestKVs], RestActions, ResultNode, [{ok, assemble(Bt, Key, Value)} | QueryOutput])
-            end
+                modify_kvnode(Bt, NodeTuple, N, RestActions, ResultNode, [{ok, assemble(Bt, Key, Value)} | QueryOutput])
+            end;
+        true ->
+            modify_kvnode(Bt, NodeTuple, N + 1, [{ActionType, ActionKey, ActionValue} | RestActions], [{Key, Value} | ResultNode], QueryOutput)
         end
     end.
 

Modified: incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_db.hrl
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_db.hrl?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_db.hrl (original)
+++ incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_db.hrl Sat Aug 16 14:01:18 2008
@@ -52,7 +52,7 @@
 -record(doc,
     {
     id = "",
-    revs = [], % in the form [{RevId, IsAvailable}, ...]
+    revs = [],
 
     % the json body object.
     body = {obj, []},

Modified: incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_view.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_view.erl?rev=686560&r1=686559&r2=686560&view=diff
==============================================================================
--- incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_view.erl (original)
+++ incubator/couchdb/branches/runtimeconfig/src/couchdb/couch_view.erl Sat Aug 16 14:01:18 2008
@@ -94,6 +94,14 @@
         N -> {ok, {reduce, N, Lang, View}}
     end.
 
+expand_dups([], Acc) ->
+    lists:reverse(Acc);
+expand_dups([{Key, {dups, Vals}} | Rest], Acc) ->
+    Expanded = [{Key, Val} || Val <- Vals],
+    expand_dups(Rest, Expanded ++ Acc);
+expand_dups([KV | Rest], Acc) ->
+    expand_dups(Rest, [KV | Acc]).
+
 fold_reduce({temp_reduce, #view{btree=Bt}}, Dir, StartKey, EndKey, GroupFun, Fun, Acc) ->
 
     WrapperFun = fun({GroupedKey, _}, PartialReds, Acc0) ->
@@ -109,7 +117,7 @@
     {_Name, FunSrc} = lists:nth(NthRed,RedFuns),
     ReduceFun =
         fun(reduce, KVs) ->
-            {ok, Reduced} = couch_query_servers:reduce(Lang, [FunSrc], KVs),
+            {ok, Reduced} = couch_query_servers:reduce(Lang, [FunSrc], expand_dups(KVs, [])),
             {0, PreResultPadding ++ Reduced ++ PostResultPadding};
         (rereduce, Reds) ->
             UserReds = [[lists:nth(NthRed, UserRedsList)] || {_, UserRedsList} <- Reds],
@@ -149,7 +157,10 @@
     {Count, _} = 
     couch_btree:final_reduce(
         fun(reduce, KVs) ->
-            {length(KVs), []};
+            Count = lists:sum(
+                [case V of {dups, Vals} -> length(Vals); _ -> 1 end
+                || {_,V} <- KVs]),
+            {Count, []};
         (rereduce, Reds) ->
             {lists:sum([Count0 || {Count0, _} <- Reds]), []}
         end, Reductions),
@@ -188,13 +199,25 @@
     Group = #group{name=Id, views=Views, def_lang=Language},
     Group#group{sig=erlang:md5(term_to_binary(Group))}.
     
-
+fold_fun(_Fun, [], _, Acc) ->
+    {ok, Acc};
+fold_fun(Fun, [KV|Rest], {KVReds, Reds}, Acc) ->
+    case Fun(KV, {KVReds, Reds}, Acc) of
+    {ok, Acc2} ->
+        fold_fun(Fun, Rest, {[KV|KVReds], Reds}, Acc2);
+    {stop, Acc2} ->
+        {stop, Acc2}
+    end.
 
 fold(#view{btree=Btree}, Dir, Fun, Acc) ->
-    {ok, _AccResult} = couch_btree:fold(Btree, Dir, Fun, Acc).
+    fold(Btree, nil, Dir, Fun, Acc).
 
 fold(#view{btree=Btree}, StartKey, Dir, Fun, Acc) ->
-    {ok, _AccResult} = couch_btree:fold(Btree, StartKey, Dir, Fun, Acc).
+    WrapperFun =
+        fun(KV, Reds, Acc2) ->
+            fold_fun(Fun, expand_dups([KV],[]), Reds, Acc2)
+        end,
+    {ok, _AccResult} = couch_btree:fold(Btree, StartKey, Dir, WrapperFun, Acc).
 
 
 init([]) ->
@@ -502,8 +525,9 @@
             FunSrcs = [FunSrc || {_Name, FunSrc} <- RedFuns],
             ReduceFun = 
                 fun(reduce, KVs) ->
-                    {ok, Reduced} = couch_query_servers:reduce(Lang, FunSrcs, KVs),
-                    {length(KVs), Reduced};
+                    KVs2 = expand_dups(KVs,[]),
+                    {ok, Reduced} = couch_query_servers:reduce(Lang, FunSrcs, KVs2),
+                    {length(KVs2), Reduced};
                 (rereduce, Reds) ->
                     Count = lists:sum([Count0 || {Count0, _} <- Reds]),
                     UserReds = [UserRedsList || {_, UserRedsList} <- Reds],
@@ -649,8 +673,25 @@
 view_insert_doc_query_results(_Doc, [], [], ViewKVsAcc, ViewIdKeysAcc) ->
     {lists:reverse(ViewKVsAcc), lists:reverse(ViewIdKeysAcc)};
 view_insert_doc_query_results(#doc{id=DocId}=Doc, [ResultKVs|RestResults], [{View, KVs}|RestViewKVs], ViewKVsAcc, ViewIdKeysAcc) ->
-    NewKVs = [{{Key, DocId}, Value} || {Key, Value} <- ResultKVs],
-    NewViewIdKeys = [{View#view.id_num, Key} || {Key, _Value} <- ResultKVs],
+    % Take any identical keys and combine the values
+    ResultKVs2 = lists:foldl(
+        fun({Key,Value}, [{PrevKey,PrevVal}|AccRest]) ->
+            case Key == PrevKey of
+            true ->
+                case PrevVal of
+                {dups, Dups} ->
+                    [{PrevKey, {dups, [Value|Dups]}} | AccRest];
+                _ ->
+                    [{PrevKey, {dups, [Value,PrevVal]}} | AccRest]
+                end;
+            false ->
+                [{Key,Value},{PrevKey,PrevVal}|AccRest]
+            end;
+        (KV, []) ->
+           [KV] 
+        end, [], lists:sort(ResultKVs)),
+    NewKVs = [{{Key, DocId}, Value} || {Key, Value} <- ResultKVs2],
+    NewViewIdKeys = [{View#view.id_num, Key} || {Key, _Value} <- ResultKVs2],
     NewViewKVsAcc = [{View, NewKVs ++ KVs} | ViewKVsAcc],
     NewViewIdKeysAcc = NewViewIdKeys ++ ViewIdKeysAcc,
     view_insert_doc_query_results(Doc, RestResults, RestViewKVs, NewViewKVsAcc, NewViewIdKeysAcc).
@@ -667,6 +708,7 @@
         {ok, QueryServerIn}
     end,
     {ok, Results} = couch_query_servers:map_docs(QueryServer, Docs),
+    
     {Group#group{query_server=QueryServer}, Results}.