summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/couchdb/default.ini.tpl.in1
-rw-r--r--etc/couchdb/local_dev.ini2
-rw-r--r--share/www/script/couch.js13
-rw-r--r--share/www/script/couch_tests.js1
-rw-r--r--share/www/script/test/changes.js38
-rw-r--r--share/www/script/test/replication.js2
-rw-r--r--src/couchdb/couch_db.erl93
-rw-r--r--src/couchdb/couch_db.hrl22
-rw-r--r--src/couchdb/couch_db_updater.erl122
-rw-r--r--src/couchdb/couch_doc.erl50
-rw-r--r--src/couchdb/couch_file.erl20
-rw-r--r--src/couchdb/couch_httpd_db.erl105
-rw-r--r--src/couchdb/couch_key_tree.erl17
-rw-r--r--src/couchdb/couch_rep.erl28
-rw-r--r--src/couchdb/couch_server.erl7
-rw-r--r--src/couchdb/couch_stats_collector.erl15
-rw-r--r--src/couchdb/couch_view_updater.erl11
17 files changed, 363 insertions, 184 deletions
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in
index 96dedce4..337886e1 100644
--- a/etc/couchdb/default.ini.tpl.in
+++ b/etc/couchdb/default.ini.tpl.in
@@ -64,6 +64,7 @@ _stats = {couch_httpd_stats_handlers, handle_stats_req}
_compact = {couch_httpd_db, handle_compact_req}
_design = {couch_httpd_db, handle_design_req}
_temp_view = {couch_httpd_view, handle_temp_view_req}
+_changes = {couch_httpd_db, handle_changes_req}
; The external module takes an optional argument allowing you to narrow it to a
; single script. Otherwise the script name is inferred from the first path section
diff --git a/etc/couchdb/local_dev.ini b/etc/couchdb/local_dev.ini
index 876295b1..11a703b0 100644
--- a/etc/couchdb/local_dev.ini
+++ b/etc/couchdb/local_dev.ini
@@ -12,7 +12,7 @@
;bind_address = 127.0.0.1
[log]
-level = error
+level = debug
[update_notification]
;unique notifier name=/full/path/to/exe -with "cmd line arg"
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index 4586cca8..8f3d96ad 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -319,16 +319,19 @@ CouchDB.replicate = function(source, target, rep_options) {
return JSON.parse(CouchDB.last_req.responseText);
}
-CouchDB.request = function(method, uri, options) {
- options = options || {};
- var req = null;
+CouchDB.newXhr = function() {
if (typeof(XMLHttpRequest) != "undefined") {
- req = new XMLHttpRequest();
+ return new XMLHttpRequest();
} else if (typeof(ActiveXObject) != "undefined") {
- req = new ActiveXObject("Microsoft.XMLHTTP");
+ return new ActiveXObject("Microsoft.XMLHTTP");
} else {
throw new Error("No XMLHTTPRequest support detected");
}
+}
+
+CouchDB.request = function(method, uri, options) {
+ options = options || {};
+ var req = CouchDB.newXhr();
req.open(method, uri, false);
if (options.headers) {
var headers = options.headers;
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 6e4699e4..62e877d8 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -60,6 +60,7 @@ loadTest("view_offsets.js");
loadTest("view_pagination.js");
loadTest("view_sandboxing.js");
loadTest("view_xml.js");
+loadTest("changes.js");
loadTest("replication.js");
loadTest("etags_head.js");
loadTest("etags_views.js");
diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js
new file mode 100644
index 00000000..fef2bcca
--- /dev/null
+++ b/share/www/script/test/changes.js
@@ -0,0 +1,38 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+couchTests.changes = function(debug) {
+ var db = new CouchDB("test_suite_db");
+ db.deleteDb();
+ db.createDb();
+ if (debug) debugger;
+
+
+ var req = CouchDB.newXhr();
+
+ req.open("GET", "/test_suite_db/_changes", false);
+ req.send("");
+ var resp = JSON.parse(req.responseText);
+
+ T(resp.results.length == 0 && resp.last_seq==0)
+
+ var doc = {_id:"foo", bar:1};
+ db.save(doc);
+
+ req.open("GET", "/test_suite_db/_changes", false);
+ req.send("");
+ var resp = JSON.parse(req.responseText);
+
+ T(resp.results.length == 1 && resp.last_seq==1)
+ T(resp.results[0].changes[0].rev == doc._rev)
+
+};
diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js
index d76d2fd8..405b0b1d 100644
--- a/share/www/script/test/replication.js
+++ b/share/www/script/test/replication.js
@@ -189,7 +189,7 @@ couchTests.replication = function(debug) {
dbB.save({_id:"foo",value:"b"});
};
- this.afterBA1 = function(dbA, dbB) {
+ this.afterBA1 = function(dbA, dbB) {
var docA = dbA.open("foo", {conflicts: true});
var docB = dbB.open("foo", {conflicts: true});
diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl
index 4cd5a5fd..e065ff3b 100644
--- a/src/couchdb/couch_db.erl
+++ b/src/couchdb/couch_db.erl
@@ -17,13 +17,14 @@
-export([open_ref_counted/2,is_idle/1,monitor/1,count_changes_since/2]).
-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([set_revs_limit/2,get_revs_limit/1]).
+-export([set_revs_limit/2,get_revs_limit/1,register_update_notifier/3]).
-export([get_missing_revs/2,name/1,doc_to_tree/1,get_update_seq/1,get_committed_update_seq/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,make_doc/2,set_admins/2,get_admins/1,ensure_full_commit/1]).
+-export([start_link/3,open_doc_int/3,set_admins/2,get_admins/1,ensure_full_commit/1]).
-export([init/1,terminate/2,handle_call/3,handle_cast/2,code_change/3,handle_info/2]).
+-export([changes_since/5]).
-include("couch_db.hrl").
@@ -81,6 +82,9 @@ is_idle(MainPid) ->
monitor(#db{main_pid=MainPid}) ->
erlang:monitor(process, MainPid).
+register_update_notifier(#db{main_pid=Pid}, Seq, Fun) ->
+ gen_server:call(Pid, {register_update_notifier, Seq, Fun}).
+
start_compact(#db{update_pid=Pid}) ->
gen_server:cast(Pid, start_compact).
@@ -166,6 +170,7 @@ get_last_purged(#db{fd=Fd, header=#db_header{purged_docs=PurgedPointer}}) ->
get_db_info(Db) ->
#db{fd=Fd,
+ header=#db_header{disk_version=DiskVersion},
compactor_pid=Compactor,
update_seq=SeqNum,
name=Name,
@@ -181,7 +186,8 @@ get_db_info(Db) ->
{purge_seq, couch_db:get_purge_seq(Db)},
{compact_running, Compactor/=nil},
{disk_size, Size},
- {instance_start_time, StartTime}
+ {instance_start_time, StartTime},
+ {disk_format_version, DiskVersion}
],
{ok, InfoList}.
@@ -337,7 +343,7 @@ prep_and_validate_updates(Db, [DocBucket|RestBuckets],
AccPrepped, AccErrors) ->
Leafs = couch_key_tree:get_all_leafs(OldRevTree),
LeafRevsDict = dict:from_list([{{Start, RevId}, {Deleted, Sp, Revs}} ||
- {{Deleted, Sp}, {Start, [RevId|_]}=Revs} <- Leafs]),
+ {{Deleted, Sp, _Seq}, {Start, [RevId|_]}=Revs} <- Leafs]),
{PreppedBucket, AccErrors3} = lists:foldl(
fun(Doc, {Docs2Acc, AccErrors2}) ->
case prep_and_validate_update(Db, Doc, OldFullDocInfo,
@@ -398,7 +404,7 @@ prep_and_validate_replicated_updates(Db, [Bucket|RestBuckets], [OldInfo|RestOldI
case PrevRevFull of
{_RevId, ?REV_MISSING} ->
conflict;
- {RevId, {IsDel, DiskSp}} ->
+ {RevId, {IsDel, DiskSp, _Seq}} ->
DiskDoc = make_doc(Db, Id, IsDel, DiskSp, PrevPath),
Doc2 = couch_doc:merge_stubs(Doc, DiskDoc),
{ok, Doc2, fun() -> DiskDoc end}
@@ -522,7 +528,7 @@ make_first_doc_on_disk(_Db, _Id, _Pos, []) ->
nil;
make_first_doc_on_disk(Db, Id, Pos, [{_Rev, ?REV_MISSING}|RestPath]) ->
make_first_doc_on_disk(Db, Id, Pos - 1, RestPath);
-make_first_doc_on_disk(Db, Id, Pos, [{_Rev, {IsDel, Sp}} |_]=DocPath) ->
+make_first_doc_on_disk(Db, Id, Pos, [{_Rev, {IsDel, Sp, _Seq}} |_]=DocPath) ->
Revs = [Rev || {Rev, _} <- DocPath],
make_doc(Db, Id, IsDel, Sp, {Pos, Revs}).
@@ -653,7 +659,22 @@ enum_docs_reduce_to_count(Reds) ->
{Count, _DelCount} = couch_btree:final_reduce(
fun couch_db_updater:btree_by_id_reduce/2, Reds),
Count.
-
+
+changes_since(Db, Style, StartSeq, Fun, Acc) ->
+ enum_docs_since(Db, StartSeq, fwd,
+ fun(DocInfo, _Offset, Acc2) ->
+ #doc_info{revs=Revs} = DocInfo,
+ case Style of
+ main_only ->
+ Infos = [DocInfo];
+ all_docs ->
+ % make each rev it's own doc info
+ Infos = [DocInfo#doc_info{revs=[RevInfo]} ||
+ #rev_info{seq=RevSeq}=RevInfo <- Revs, StartSeq < RevSeq]
+ end,
+ Fun(Infos, Acc2)
+ end, Acc).
+
count_changes_since(Db, SinceSeq) ->
{ok, Changes} =
couch_btree:fold_reduce(Db#db.docinfo_by_seq_btree,
@@ -665,9 +686,9 @@ count_changes_since(Db, SinceSeq) ->
end,
0),
Changes.
-
-enum_docs_since(Db, SinceSeq, Direction, InFun, Ctx) ->
- couch_btree:fold(Db#db.docinfo_by_seq_btree, SinceSeq + 1, Direction, InFun, Ctx).
+
+enum_docs_since(Db, SinceSeq, Direction, InFun, Acc) ->
+ couch_btree:fold(Db#db.docinfo_by_seq_btree, SinceSeq + 1, Direction, InFun, Acc).
enum_docs_since(Db, SinceSeq, InFun, Acc) ->
enum_docs_since(Db, SinceSeq, fwd, InFun, Acc).
@@ -684,6 +705,7 @@ init({DbName, Filepath, Fd, Options}) ->
{ok, UpdaterPid} = gen_server:start_link(couch_db_updater, {self(), DbName, Filepath, Fd, Options}, []),
{ok, #db{fd_ref_counter=RefCntr}=Db} = gen_server:call(UpdaterPid, get_db),
couch_ref_counter:add(RefCntr),
+ couch_stats_collector:track_process_count({couchdb, open_databases}),
{ok, Db}.
terminate(Reason, _Db) ->
@@ -698,8 +720,8 @@ handle_call(is_idle, _From, #db{fd_ref_counter=RefCntr, compactor_pid=Compact,
% Idle means no referrers. Unless in the middle of a compaction file switch,
% there are always at least 2 referrers, couch_db_updater and us.
{reply, (Delay == nil) and (Compact == nil) and (couch_ref_counter:count(RefCntr) == 2), Db};
-handle_call({db_updated, #db{fd_ref_counter=NewRefCntr}=NewDb}, _From,
- #db{fd_ref_counter=OldRefCntr}) ->
+handle_call({db_updated, NewDb}, _From, #db{fd_ref_counter=OldRefCntr}) ->
+ #db{fd_ref_counter=NewRefCntr}=NewDb,
case NewRefCntr == OldRefCntr of
true -> ok;
false ->
@@ -747,7 +769,7 @@ open_doc_revs_int(Db, IdRevs, Options) ->
?REV_MISSING ->
% we have the rev in our list but know nothing about it
{{not_found, missing}, {Pos, Rev}};
- {IsDeleted, SummaryPtr} ->
+ {IsDeleted, SummaryPtr, _UpdateSeq} ->
{ok, make_doc(Db, Id, IsDeleted, SummaryPtr, FoundRevPath)}
end
end, FoundRevs),
@@ -768,14 +790,15 @@ open_doc_int(Db, <<?LOCAL_DOC_PREFIX, _/binary>> = Id, _Options) ->
[not_found] ->
{not_found, missing}
end;
-open_doc_int(Db, #doc_info{id=Id,rev={Pos,RevId},deleted=IsDeleted,summary_pointer=Sp}=DocInfo, Options) ->
- Doc = make_doc(Db, Id, IsDeleted, Sp, {Pos,[RevId]}),
+open_doc_int(Db, #doc_info{id=Id,revs=[RevInfo|_]}=DocInfo, Options) ->
+ #rev_info{deleted=IsDeleted,rev={Pos,RevId},body_sp=Bp} = RevInfo,
+ Doc = make_doc(Db, Id, IsDeleted, Bp, {Pos,[RevId]}),
{ok, Doc#doc{meta=doc_meta_info(DocInfo, [], Options)}};
open_doc_int(Db, #full_doc_info{id=Id,rev_tree=RevTree}=FullDocInfo, Options) ->
- #doc_info{deleted=IsDeleted,rev=Rev,summary_pointer=Sp} = DocInfo =
- couch_doc:to_doc_info(FullDocInfo),
+ #doc_info{revs=[#rev_info{deleted=IsDeleted,rev=Rev,body_sp=Bp}|_]} =
+ DocInfo = couch_doc:to_doc_info(FullDocInfo),
{[{_, RevPath}], []} = couch_key_tree:get(RevTree, [Rev]),
- Doc = make_doc(Db, Id, IsDeleted, Sp, RevPath),
+ Doc = make_doc(Db, Id, IsDeleted, Bp, RevPath),
{ok, Doc#doc{meta=doc_meta_info(DocInfo, RevTree, Options)}};
open_doc_int(Db, Id, Options) ->
case get_full_doc_info(Db, Id) of
@@ -785,36 +808,36 @@ open_doc_int(Db, Id, Options) ->
{not_found, missing}
end.
-doc_meta_info(DocInfo, RevTree, Options) ->
+doc_meta_info(#doc_info{revs=[#rev_info{rev=Rev}|RestInfo]}, RevTree, Options) ->
case lists:member(revs_info, Options) of
false -> [];
true ->
{[{Pos, RevPath}],[]} =
- couch_key_tree:get_full_key_paths(RevTree, [DocInfo#doc_info.rev]),
+ couch_key_tree:get_full_key_paths(RevTree, [Rev]),
[{revs_info, Pos, lists:map(
- fun({Rev, {true, _Sp}}) ->
- {Rev, deleted};
- ({Rev, {false, _Sp}}) ->
- {Rev, available};
- ({Rev, ?REV_MISSING}) ->
- {Rev, missing}
+ fun({Rev1, {true, _Sp, _UpdateSeq}}) ->
+ {Rev1, deleted};
+ ({Rev1, {false, _Sp, _UpdateSeq}}) ->
+ {Rev1, available};
+ ({Rev1, ?REV_MISSING}) ->
+ {Rev1, missing}
end, RevPath)}]
end ++
case lists:member(conflicts, Options) of
false -> [];
true ->
- case DocInfo#doc_info.conflict_revs of
+ case [Rev1 || #rev_info{rev=Rev1,deleted=false} <- RestInfo] of
[] -> [];
- _ -> [{conflicts, DocInfo#doc_info.conflict_revs}]
+ ConflictRevs -> [{conflicts, ConflictRevs}]
end
end ++
case lists:member(deleted_conflicts, Options) of
false -> [];
true ->
- case DocInfo#doc_info.deleted_conflict_revs of
+ case [Rev1 || #rev_info{rev=Rev1,deleted=false} <- RestInfo] of
[] -> [];
- _ -> [{deleted_conflicts, DocInfo#doc_info.deleted_conflict_revs}]
+ DelConflictRevs -> [{deleted_conflicts, DelConflictRevs}]
end
end.
@@ -829,19 +852,15 @@ doc_to_tree_simple(Doc, [RevId]) ->
doc_to_tree_simple(Doc, [RevId | Rest]) ->
[{RevId, ?REV_MISSING, doc_to_tree_simple(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) ->
+make_doc(#db{fd=Fd}=Db, Id, Deleted, Bp, RevisionPath) ->
{BodyData, BinValues} =
- case BodySp of
+ case Bp of
nil ->
{[], []};
_ ->
{ok, {BodyData0, BinValues0}} =
- couch_stream:read_term( Db#db.summary_stream, BodySp),
+ couch_stream:read_term( Db#db.summary_stream, Bp),
{BodyData0,
[{Name,{Type,{Fd,Sp,Len}}} || {Name,{Type,Sp,Len}} <- BinValues0]}
end,
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index b23fd18d..c00792a8 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -37,15 +37,20 @@
-define(LOG_ERROR(Format, Args),
error_logger:error_report(couch_error, {Format, Args})).
+
+-record(rev_info,
+ {
+ rev,
+ seq = 0,
+ deleted = false,
+ body_sp = nil % stream pointer
+ }).
+
-record(doc_info,
{
id = <<"">>,
- rev = <<"">>,
- update_seq = 0,
- summary_pointer = nil,
- conflict_revs = [],
- deleted_conflict_revs = [],
- deleted = false
+ high_seq = 0,
+ revs = [] % rev_info
}).
-record(full_doc_info,
@@ -98,13 +103,14 @@
% than filling in new defaults.
%
% As long the changes are limited to new header fields (with inline
-% defaults) added to the end of the file, then there is no need to increment
+% defaults) added to the end of the record, then there is no need to increment
% the disk revision number.
%
% if the disk revision is incremented, then new upgrade logic will need to be
% added to couch_db_updater:init_db.
--define(LATEST_DISK_VERSION, 1).
+-define(DISK_VERSION_0_9, 1).
+-define(LATEST_DISK_VERSION, 2).
-record(db_header,
{disk_version = ?LATEST_DISK_VERSION,
diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl
index 1267ffa4..b1cb9037 100644
--- a/src/couchdb/couch_db_updater.erl
+++ b/src/couchdb/couch_db_updater.erl
@@ -115,13 +115,16 @@ handle_call({purge_docs, IdRevs}, _From, Db) ->
|| {#full_doc_info{id=Id}, Revs} <- NewDocInfos],
{DocInfoToUpdate, NewSeq} = lists:mapfoldl(
- fun(FullInfo, SeqAcc) ->
- Info = couch_doc:to_doc_info(FullInfo),
- {Info#doc_info{update_seq=SeqAcc + 1}, SeqAcc + 1}
+ fun(#full_doc_info{rev_tree=Tree}=FullInfo, SeqAcc) ->
+ Tree2 = couch_key_tree:map_leafs( fun(RevInfo) ->
+ RevInfo#rev_info{seq=SeqAcc + 1}
+ end, Tree),
+ {couch_doc:to_doc_info(FullInfo#full_doc_info{rev_tree=Tree2}),
+ SeqAcc + 1}
end, LastSeq, FullDocInfoToUpdate),
- IdsToRemove = [Id || {#full_doc_info{id=Id,rev_tree=Tree},_}
- <- NewDocInfos, Tree == []],
+ IdsToRemove = [Id || {#full_doc_info{id=Id,rev_tree=[]},_}
+ <- NewDocInfos],
{ok, DocInfoBySeqBTree2} = couch_btree:add_remove(DocInfoBySeqBTree,
DocInfoToUpdate, SeqsToRemove),
@@ -194,33 +197,61 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-btree_by_seq_split(DocInfo) ->
- #doc_info{
- id = Id,
- rev = Rev,
- update_seq = Seq,
- summary_pointer = Sp,
- conflict_revs = Conflicts,
- deleted_conflict_revs = DelConflicts,
- deleted = Deleted} = DocInfo,
- {Seq,{Id, Rev, Sp, Conflicts, DelConflicts, Deleted}}.
+btree_by_seq_split(#doc_info{id=Id, high_seq=KeySeq, revs=Revs}) ->
+ RevInfos = [{Rev, Seq, Bp} ||
+ #rev_info{rev=Rev,seq=Seq,deleted=false,body_sp=Bp} <- Revs],
+ DeletedRevInfos = [{Rev, Seq, Bp} ||
+ #rev_info{rev=Rev,seq=Seq,deleted=true,body_sp=Bp} <- Revs],
+ {KeySeq,{Id, RevInfos, DeletedRevInfos}}.
-btree_by_seq_join(Seq,{Id, Rev, Sp, Conflicts, DelConflicts, Deleted}) ->
+btree_by_seq_join(KeySeq, {Id, RevInfos, DeletedRevInfos}) ->
#doc_info{
id = Id,
- rev = Rev,
- update_seq = Seq,
- summary_pointer = Sp,
- conflict_revs = Conflicts,
- deleted_conflict_revs = DelConflicts,
- deleted = Deleted}.
+ high_seq=KeySeq,
+ revs =
+ [#rev_info{rev=Rev,seq=Seq,deleted=false,body_sp = Bp} ||
+ {Rev, Seq, Bp} <- RevInfos] ++
+ [#rev_info{rev=Rev,seq=Seq,deleted=true,body_sp = Bp} ||
+ {Rev, Seq, Bp} <- DeletedRevInfos]};
+btree_by_seq_join(KeySeq,{Id, Rev, Bp, Conflicts, DelConflicts, Deleted}) ->
+ % this is the 0.9.0 and earlier by_seq record. It's missing the body pointers
+ % and individual seq nums for conflicts that are currently in the index,
+ % meaning the filtered _changes api will not work except for on main docs.
+ % Simply compact a 0.9.0 database to upgrade the index.
+ #doc_info{
+ id=Id,
+ high_seq=KeySeq,
+ revs = [#rev_info{rev=Rev,seq=KeySeq,deleted=Deleted,body_sp=Bp}] ++
+ [#rev_info{rev=Rev1,seq=KeySeq,deleted=false} || Rev1 <- Conflicts] ++
+ [#rev_info{rev=Rev2,seq=KeySeq,deleted=true} || Rev2 <- DelConflicts]}.
btree_by_id_split(#full_doc_info{id=Id, update_seq=Seq,
deleted=Deleted, rev_tree=Tree}) ->
- {Id, {Seq, case Deleted of true -> 1; false-> 0 end, Tree}}.
-
-btree_by_id_join(Id, {Seq, Deleted, Tree}) ->
- #full_doc_info{id=Id, update_seq=Seq, deleted=Deleted==1, rev_tree=Tree}.
+ DiskTree =
+ couch_key_tree:map(
+ fun(_RevId, {IsDeleted, BodyPointer, UpdateSeq}) ->
+ {if IsDeleted -> 1; true -> 0 end, BodyPointer, UpdateSeq};
+ (_RevId, ?REV_MISSING) ->
+ ?REV_MISSING
+ end, Tree),
+ {Id, {Seq, if Deleted -> 1; true -> 0 end, DiskTree}}.
+
+btree_by_id_join(Id, {HighSeq, Deleted, DiskTree}) ->
+ Tree =
+ couch_key_tree:map(
+ fun(_RevId, {IsDeleted, BodyPointer, UpdateSeq}) ->
+ {IsDeleted == 1, BodyPointer, UpdateSeq};
+ (_RevId, ?REV_MISSING) ->
+ ?REV_MISSING;
+ (_RevId, {IsDeleted, BodyPointer}) ->
+ % this is the 0.9.0 and earlier rev info record. It's missing the seq
+ % nums, which means couchdb will sometimes reexamine unchanged
+ % documents with the _changes API.
+ % This is fixed by compacting the database.
+ {IsDeleted, BodyPointer, HighSeq}
+ end, DiskTree),
+
+ #full_doc_info{id=Id, update_seq=HighSeq, deleted=Deleted==1, rev_tree=Tree}.
btree_by_id_reduce(reduce, FullDocInfos) ->
% count the number of not deleted documents
@@ -251,6 +282,7 @@ less_docid(A, B) -> A < B.
init_db(DbName, Filepath, Fd, Header0) ->
case element(2, Header0) of
+ ?DISK_VERSION_0_9 -> ok; % no problem, all records upgrade on the fly
?LATEST_DISK_VERSION -> ok;
_ -> throw({database_disk_version_error, "Incorrect disk header version"})
end,
@@ -320,7 +352,8 @@ refresh_validate_doc_funs(Db) ->
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]};
+ {ok, Doc} = couch_db:open_doc_int(Db, FullDocInfo, []),
+ {ok, [Doc | AccDocs]};
(_, _Reds, AccDocs) ->
{stop, AccDocs}
end,
@@ -331,8 +364,8 @@ get_design_docs(#db{fulldocinfo_by_id_btree=Btree}=Db) ->
flush_trees(_Db, [], AccFlushedTrees) ->
{ok, lists:reverse(AccFlushedTrees)};
flush_trees(#db{fd=Fd}=Db, [InfoUnflushed | RestUnflushed], AccFlushed) ->
- #full_doc_info{rev_tree=Unflushed} = InfoUnflushed,
- Flushed = couch_key_tree:map(
+ #full_doc_info{update_seq=UpdateSeq, rev_tree=Unflushed} = InfoUnflushed,
+ Flushed = couch_key_tree:map(
fun(_Rev, Value) ->
case Value of
#doc{attachments=Atts,deleted=IsDeleted}=Doc ->
@@ -355,7 +388,7 @@ flush_trees(#db{fd=Fd}=Db, [InfoUnflushed | RestUnflushed], AccFlushed) ->
throw(retry)
end,
{ok, NewSummaryPointer} = couch_stream:write_term(Db#db.summary_stream, {Doc#doc.body, Bins}),
- {IsDeleted, NewSummaryPointer};
+ {IsDeleted, NewSummaryPointer, UpdateSeq};
_ ->
Value
end
@@ -394,10 +427,13 @@ merge_rev_trees(MergeConflicts, [NewDocs|RestDocsList],
[NewInfo|AccNewInfos], RemoveSeqs, NewConflicts, AccSeq+1)
end.
+
+
new_index_entries([], AccById, AccBySeq) ->
- {ok, AccById, AccBySeq};
+ {AccById, AccBySeq};
new_index_entries([FullDocInfo|RestInfos], AccById, AccBySeq) ->
- #doc_info{deleted=Deleted} = DocInfo = couch_doc:to_doc_info(FullDocInfo),
+ #doc_info{revs=[#rev_info{deleted=Deleted}|_]} = DocInfo =
+ couch_doc:to_doc_info(FullDocInfo),
new_index_entries(RestInfos,
[FullDocInfo#full_doc_info{deleted=Deleted}|AccById],
[DocInfo|AccBySeq]).
@@ -436,13 +472,13 @@ update_docs_int(Db, DocsList, Options) ->
#full_doc_info{id=Id}
end,
Ids, OldDocLookups),
-
+
% Merge the new docs into the revision trees.
{ok, NewDocInfos0, RemoveSeqs, Conflicts, NewSeq} = merge_rev_trees(
lists:member(merge_conflicts, Options),
DocsList2, OldDocInfos, [], [], [], LastSeq),
- NewDocInfos = stem_full_doc_infos(Db, NewDocInfos0),
+ NewFullDocInfos = stem_full_doc_infos(Db, NewDocInfos0),
% All documents are now ready to write.
@@ -450,13 +486,14 @@ update_docs_int(Db, DocsList, Options) ->
% Write out the document summaries (the bodies are stored in the nodes of
% the trees, the attachments are already written to disk)
- {ok, FlushedDocInfos} = flush_trees(Db2, NewDocInfos, []),
+ {ok, FlushedFullDocInfos} = flush_trees(Db2, NewFullDocInfos, []),
- {ok, InfoById, InfoBySeq} = new_index_entries(FlushedDocInfos, [], []),
+ {IndexFullDocInfos, IndexDocInfos} =
+ new_index_entries(FlushedFullDocInfos, [], []),
% and the indexes
- {ok, DocInfoBySeqBTree2} = couch_btree:add_remove(DocInfoBySeqBTree, InfoBySeq, RemoveSeqs),
- {ok, DocInfoByIdBTree2} = couch_btree:add_remove(DocInfoByIdBTree, InfoById, []),
+ {ok, DocInfoByIdBTree2} = couch_btree:add_remove(DocInfoByIdBTree, IndexFullDocInfos, []),
+ {ok, DocInfoBySeqBTree2} = couch_btree:add_remove(DocInfoBySeqBTree, IndexDocInfos, RemoveSeqs),
Db3 = Db2#db{
fulldocinfo_by_id_btree = DocInfoByIdBTree2,
@@ -474,7 +511,8 @@ update_docs_int(Db, DocsList, Options) ->
{ok, LocalConflicts ++ Conflicts,
commit_data(Db4, not lists:member(full_commit, Options))}.
-
+
+
update_local_docs(#db{local_docs_btree=Btree}=Db, Docs) ->
Ids = [Id || #doc{id=Id} <- Docs],
OldDocLookups = couch_btree:lookup(Btree, Ids),
@@ -558,10 +596,10 @@ copy_rev_tree(SrcFd, DestFd, DestStream, [{Start, Tree} | RestTree]) ->
% root nner node, only copy info/data from leaf nodes
[Tree2] = copy_rev_tree(SrcFd, DestFd, DestStream, [Tree]),
[{Start, Tree2} | copy_rev_tree(SrcFd, DestFd, DestStream, RestTree)];
-copy_rev_tree(SrcFd, DestFd, DestStream, [{RevId, {IsDel, Sp}, []} | RestTree]) ->
+copy_rev_tree(SrcFd, DestFd, DestStream, [{RevId, {IsDel, Sp, Seq}, []} | RestTree]) ->
% This is a leaf node, copy it over
NewSp = copy_raw_doc(SrcFd, Sp, DestFd, DestStream),
- [{RevId, {IsDel, NewSp}, []} | copy_rev_tree(SrcFd, DestFd, DestStream, RestTree)];
+ [{RevId, {IsDel, NewSp, Seq}, []} | copy_rev_tree(SrcFd, DestFd, DestStream, RestTree)];
copy_rev_tree(SrcFd, DestFd, DestStream, [{RevId, _, SubTree} | RestTree]) ->
% inner node, only copy info/data from leaf nodes
[{RevId, ?REV_MISSING, copy_rev_tree(SrcFd, DestFd, DestStream, SubTree)} | copy_rev_tree(SrcFd, DestFd, DestStream, RestTree)].
@@ -598,7 +636,7 @@ copy_docs(#db{fd=SrcFd}=Db, #db{fd=DestFd,summary_stream=DestStream}=NewDb, Info
copy_compact(Db, NewDb, Retry) ->
TotalChanges = couch_db:count_changes_since(Db, NewDb#db.update_seq),
EnumBySeqFun =
- fun(#doc_info{update_seq=Seq}=DocInfo, _Offset, {AccNewDb, AccUncopied, TotalCopied}) ->
+ fun(#doc_info{high_seq=Seq}=DocInfo, _Offset, {AccNewDb, AccUncopied, TotalCopied}) ->
couch_task_status:update("Copied ~p of ~p changes (~p%)",
[TotalCopied, TotalChanges, (TotalCopied*100) div TotalChanges]),
if TotalCopied rem 1000 == 0 ->
diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl
index 307f7372..4d2affa4 100644
--- a/src/couchdb/couch_doc.erl
+++ b/src/couchdb/couch_doc.erl
@@ -226,34 +226,28 @@ to_doc_info(FullDocInfo) ->
{DocInfo, _Path} = to_doc_info_path(FullDocInfo),
DocInfo.
-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({{IsDeletedA, _}, {StartA, [RevIdA|_]}}, {{IsDeletedB, _}, {StartB, [RevIdB|_]}}) ->
- % sort descending by {not deleted, then Depth, then RevisionId}
- A = {not IsDeletedA, StartA, RevIdA},
- B = {not IsDeletedB, StartB, RevIdB},
- A > B
- end,
- LeafRevs),
-
- [{{IsDeleted, SummaryPointer}, {Start, [RevId|_]}=Path} | Rest] = SortedLeafRevs,
- {ConflictRevTuples, DeletedConflictRevTuples} =
- lists:splitwith(fun({{IsDeleted1, _Sp}, _}) ->
- not IsDeleted1
- end, Rest),
-
- ConflictRevs = [{Start1, RevId1} || {_, {Start1, [RevId1|_]}} <- ConflictRevTuples],
- DeletedConflictRevs = [{Start1, RevId1} || {_, {Start1, [RevId1|_]}} <- DeletedConflictRevTuples],
- DocInfo = #doc_info{
- id=Id,
- update_seq=Seq,
- rev = {Start, RevId},
- summary_pointer = SummaryPointer,
- conflict_revs = ConflictRevs,
- deleted_conflict_revs = DeletedConflictRevs,
- deleted = IsDeleted},
- {DocInfo, Path}.
+max_seq([], Max) ->
+ Max;
+max_seq([#rev_info{seq=Seq}|Rest], Max) ->
+ max_seq(Rest, if Max > Seq -> Max; true -> Seq end).
+
+to_doc_info_path(#full_doc_info{id=Id,rev_tree=Tree}) ->
+ RevInfosAndPath =
+ [{#rev_info{deleted=Del,body_sp=Bp,seq=Seq,rev={Pos,RevId}}, Path} ||
+ {{Del, Bp, Seq},{Pos, [RevId|_]}=Path} <-
+ couch_key_tree:get_all_leafs(Tree)],
+ SortedRevInfosAndPath = lists:sort(
+ fun({#rev_info{deleted=DeletedA,rev=RevA}, _PathA},
+ {#rev_info{deleted=DeletedB,rev=RevB}, _PathB}) ->
+ % sort descending by {not deleted, rev}
+ {not DeletedA, RevA} > {not DeletedB, RevB}
+ end, RevInfosAndPath),
+ [{_RevInfo, WinPath}|_] = SortedRevInfosAndPath,
+ RevInfos = [RevInfo || {RevInfo, _Path} <- SortedRevInfosAndPath],
+ {#doc_info{id=Id, high_seq=max_seq(RevInfos, 0), revs=RevInfos}, WinPath}.
+
+
+
bin_foldl(Bin, Fun, Acc) when is_binary(Bin) ->
case Fun(Bin, Acc) of
diff --git a/src/couchdb/couch_file.erl b/src/couchdb/couch_file.erl
index b9da5488..430aa6b7 100644
--- a/src/couchdb/couch_file.erl
+++ b/src/couchdb/couch_file.erl
@@ -284,14 +284,16 @@ init({Filepath, Options, ReturnPid, Ref}) ->
true ->
{ok, 0} = file:position(Fd, 0),
ok = file:truncate(Fd),
- track_stats(),
+ couch_stats_collector:track_process_count(
+ {couchdb, open_os_files}),
{ok, Fd};
false ->
ok = file:close(Fd),
init_status_error(ReturnPid, Ref, file_exists)
end;
false ->
- track_stats(),
+ couch_stats_collector:track_process_count(
+ {couchdb, open_os_files}),
{ok, Fd}
end;
Error ->
@@ -303,7 +305,7 @@ init({Filepath, Options, ReturnPid, Ref}) ->
{ok, Fd_Read} ->
{ok, Fd} = file:open(Filepath, [read, write, raw, binary]),
ok = file:close(Fd_Read),
- track_stats(),
+ couch_stats_collector:track_process_count({couchdb, open_os_files}),
{ok, Fd};
Error ->
init_status_error(ReturnPid, Ref, Error)
@@ -314,18 +316,6 @@ init({Filepath, Options, ReturnPid, Ref}) ->
terminate(_Reason, _Fd) ->
ok.
-track_stats() ->
- case (catch couch_stats_collector:increment({couchdb, open_os_files})) of
- ok ->
- Self = self(),
- spawn(
- fun() ->
- erlang:monitor(process, Self),
- receive {'DOWN', _, _, _, _} -> ok end,
- couch_stats_collector:decrement({couchdb, open_os_files})
- end);
- _ -> ok
- end.
handle_call({pread, Pos, Bytes}, _From, Fd) ->
{reply, file:pread(Fd, Pos, Bytes), Fd};
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 4295ed77..b129d37e 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -14,7 +14,7 @@
-include("couch_db.hrl").
-export([handle_request/1, handle_compact_req/2, handle_design_req/2,
- db_req/2, couch_doc_open/4]).
+ db_req/2, couch_doc_open/4,handle_changes_req/2]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -42,6 +42,72 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
do_db_req(Req, Handler)
end.
+handle_changes_req(#httpd{method='GET',path_parts=[DbName|_]}=Req, Db) ->
+ StartSeq = list_to_integer(couch_httpd:qs_value(Req, "since", "0")),
+
+ {ok, Resp} = start_json_response(Req, 200),
+ send_chunk(Resp, "{\"results\":[\n"),
+ case couch_httpd:qs_value(Req, "continuous", "false") of
+ "true" ->
+ Self = self(),
+ Notify = couch_db_update_notifier:start_link(
+ fun({_, DbName0}) when DbName0 == DbName ->
+ Self ! db_updated;
+ (_) ->
+ ok
+ end),
+ try
+ keep_sending_changes(Req, Resp, Db, StartSeq, <<"">>)
+ after
+ catch couch_db_update_notifier:stop(Notify),
+ wait_db_updated(0) % clean out any remaining update messages
+ end;
+ "false" ->
+ {ok, {LastSeq, _Prepend}} =
+ send_changes(Req, Resp, Db, StartSeq, <<"">>),
+ send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":~w}\n", [LastSeq])),
+ send_chunk(Resp, "")
+ end;
+
+handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+% waits for a db_updated msg, if there are multiple msgs, collects them.
+wait_db_updated(Timeout) ->
+ receive db_updated ->
+ wait_db_updated(0)
+ after Timeout -> ok
+ end.
+
+keep_sending_changes(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Resp, Db, StartSeq, Prepend) ->
+ {ok, {EndSeq, Prepend2}} = send_changes(Req, Resp, Db, StartSeq, Prepend),
+ couch_db:close(Db),
+ wait_db_updated(infinity),
+ {ok, Db2} = couch_db:open(DbName, [{user_ctx, UserCtx}]),
+ keep_sending_changes(Req, Resp, Db2, EndSeq, Prepend2).
+
+send_changes(Req, Resp, Db, StartSeq, Prepend0) ->
+ Style = list_to_existing_atom(
+ couch_httpd:qs_value(Req, "style", "main_only")),
+ couch_db:changes_since(Db, Style, StartSeq,
+ fun([#doc_info{id=Id, high_seq=Seq}|_]=DocInfos, {_, Prepend}) ->
+ FilterFun =
+ fun(#doc_info{revs=[#rev_info{rev=Rev}|_]}) ->
+ {[{rev, couch_doc:rev_to_str(Rev)}]}
+ end,
+ Results0 = [FilterFun(DocInfo) || DocInfo <- DocInfos],
+ Results = [Result || Result <- Results0, Result /= null],
+ case Results of
+ [] ->
+ {ok, {Seq, Prepend}};
+ _ ->
+ send_chunk(Resp,
+ [Prepend, ?JSON_ENCODE({[{seq,Seq}, {id, Id},
+ {changes,Results}]})]),
+ {ok, {Seq, <<",\n">>}}
+ end
+ end, {StartSeq, Prepend0}).
+
handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
ok = couch_view_compactor:start_compact(DbName, Id),
send_json(Req, 202, {[{ok, true}]});
@@ -89,7 +155,7 @@ do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) ->
try
Fun(Req, Db)
after
- couch_db:close(Db)
+ catch couch_db:close(Db)
end;
Error ->
throw(Error)
@@ -258,7 +324,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
db_req(#httpd{path_parts=[_,<<"_all_docs">>]}=Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD,POST");
-
+
db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
#view_query_args{
start_key = StartKey,
@@ -285,28 +351,29 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_all_docs_by_seq">>]}=Req, Db) ->
fun(DocInfo, Offset, Acc) ->
#doc_info{
id=Id,
- rev=Rev,
- update_seq=UpdateSeq,
- deleted=Deleted,
- conflict_revs=ConflictRevs,
- deleted_conflict_revs=DelConflictRevs
+ high_seq=Seq,
+ revs=[#rev_info{rev=Rev,deleted=Deleted} | RestInfo]
} = DocInfo,
+ ConflictRevs = couch_doc:rev_to_strs(
+ [Rev1 || #rev_info{deleted=false, rev=Rev1} <- RestInfo]),
+ DelConflictRevs = couch_doc:rev_to_strs(
+ [Rev1 || #rev_info{deleted=true, rev=Rev1} <- RestInfo]),
Json = {
[{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++
case ConflictRevs of
- [] -> [];
- _ -> [{<<"conflicts">>, couch_doc:rev_to_strs(ConflictRevs)}]
+ [] -> [];
+ _ -> [{<<"conflicts">>, ConflictRevs}]
end ++
case DelConflictRevs of
- [] -> [];
- _ -> [{<<"deleted_conflicts">>, couch_doc:rev_to_strs(DelConflictRevs)}]
+ [] -> [];
+ _ -> [{<<"deleted_conflicts">>, DelConflictRevs}]
end ++
case Deleted of
- true -> [{<<"deleted">>, true}];
- false -> []
+ true -> [{<<"deleted">>, true}];
+ false -> []
end
},
- FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
+ FoldlFun({{Seq, Id}, Json}, Offset, Acc)
end, {Limit, SkipCount, undefined, []}),
couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
end);
@@ -412,9 +479,9 @@ all_docs_view(Req, Db, Keys) ->
}),
AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
case couch_doc:to_doc_info(FullDocInfo) of
- #doc_info{deleted=false, rev=Rev} ->
+ #doc_info{revs=[#rev_info{deleted=false, rev=Rev}|_]} ->
FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
- #doc_info{deleted=true} ->
+ #doc_info{revs=[#rev_info{deleted=true}|_]} ->
{ok, Acc}
end
end,
@@ -436,9 +503,9 @@ all_docs_view(Req, Db, Keys) ->
fun(Key, {ok, FoldAcc}) ->
DocInfo = (catch couch_db:get_doc_info(Db, Key)),
Doc = case DocInfo of
- {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
+ {ok, #doc_info{id=Id, revs=[#rev_info{deleted=false, rev=Rev}|_]}} ->
{{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
- {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
+ {ok, #doc_info{id=Id, revs=[#rev_info{deleted=true, rev=Rev}|_]}} ->
{{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
not_found ->
{{Key, error}, not_found};
diff --git a/src/couchdb/couch_key_tree.erl b/src/couchdb/couch_key_tree.erl
index baea9e3f..7c6a2dc1 100644
--- a/src/couchdb/couch_key_tree.erl
+++ b/src/couchdb/couch_key_tree.erl
@@ -14,7 +14,7 @@
-export([merge/2, find_missing/2, get_key_leafs/2, get_full_key_paths/2, get/2]).
-export([map/2, get_all_leafs/1, count_leafs/1, remove_leafs/2,
- get_all_leafs_full/1,stem/2,test/0]).
+ get_all_leafs_full/1,stem/2,map_leafs/2,test/0]).
% a key tree looks like this:
% Tree -> [] or [{Key, Value, ChildTree} | SiblingTree]
@@ -291,6 +291,21 @@ map_simple(Fun, Pos, [{Key, Value, SubTree} | RestTree]) ->
Value2 = Fun({Pos, Key}, Value),
[{Key, Value2, map_simple(Fun, Pos + 1, SubTree)} | map_simple(Fun, Pos, RestTree)].
+
+map_leafs(_Fun, []) ->
+ [];
+map_leafs(Fun, [{Pos, Tree}|Rest]) ->
+ [NewTree] = map_leafs_simple(Fun, Pos, [Tree]),
+ [{Pos, NewTree} | map_leafs(Fun, Rest)].
+
+map_leafs_simple(_Fun, _Pos, []) ->
+ [];
+map_leafs_simple(Fun, Pos, [{Key, Value, []} | RestTree]) ->
+ Value2 = Fun({Pos, Key}, Value),
+ [{Key, Value2, []} | map_leafs_simple(Fun, Pos, RestTree)];
+map_leafs_simple(Fun, Pos, [{Key, Value, SubTree} | RestTree]) ->
+ [{Key, Value, map_leafs_simple(Fun, Pos + 1, SubTree)} | map_leafs_simple(Fun, Pos, RestTree)].
+
stem(Trees, Limit) ->
% flatten each branch in a tree into a tree path
diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl
index f7b1e9ae..721e3071 100644
--- a/src/couchdb/couch_rep.erl
+++ b/src/couchdb/couch_rep.erl
@@ -605,22 +605,17 @@ enum_docs_since(Pid, DbSource, DbTarget, {StartSeq, RevsCount}) ->
[] ->
gen_server:call(Pid, {fin, {StartSeq, RevsCount}}, infinity);
DocInfoList ->
- SrcRevsList = lists:map(fun(SrcDocInfo) ->
- #doc_info{id=Id,
- rev=Rev,
- conflict_revs=Conflicts,
- deleted_conflict_revs=DelConflicts
- } = SrcDocInfo,
- SrcRevs = [Rev | Conflicts] ++ DelConflicts,
+ SrcRevsList = lists:map(fun(#doc_info{id=Id,revs=RevInfos}) ->
+ SrcRevs = [Rev || #rev_info{rev=Rev} <- RevInfos],
{Id, SrcRevs}
end, DocInfoList),
{ok, MissingRevs} = get_missing_revs(DbTarget, SrcRevsList),
%% do we need to check for success here?
- [ gen_server:call(Pid, {replicate_doc, Info}, infinity)
+ [gen_server:call(Pid, {replicate_doc, Info}, infinity)
|| Info <- MissingRevs ],
- #doc_info{update_seq=LastSeq} = lists:last(DocInfoList),
+ #doc_info{high_seq=LastSeq} = lists:last(DocInfoList),
RevsCount2 = RevsCount + length(SrcRevsList),
gen_server:cast(Pid, {increment_update_seq, LastSeq}),
@@ -641,15 +636,15 @@ get_doc_info_list(#http_db{uri=DbUrl, headers=Headers}, StartSeq) ->
{Results} = do_http_request(Url, get, Headers),
lists:map(fun({RowInfoList}) ->
{RowValueProps} = proplists:get_value(<<"value">>, RowInfoList),
+ Seq = proplists:get_value(<<"key">>, RowInfoList),
+ Revs =
+ [#rev_info{rev=couch_doc:parse_rev(proplists:get_value(<<"rev">>, RowValueProps)), deleted = proplists:get_value(<<"deleted">>, RowValueProps, false)} |
+ [#rev_info{rev=Rev,deleted=false} || Rev <- couch_doc:parse_revs(proplists:get_value(<<"conflicts">>, RowValueProps, []))] ++
+ [#rev_info{rev=Rev,deleted=true} || Rev <- couch_doc:parse_revs(proplists:get_value(<<"deleted_conflicts">>, RowValueProps, []))]],
#doc_info{
id=proplists:get_value(<<"id">>, RowInfoList),
- rev=couch_doc:parse_rev(proplists:get_value(<<"rev">>, RowValueProps)),
- update_seq = proplists:get_value(<<"key">>, RowInfoList),
- conflict_revs =
- couch_doc:parse_revs(proplists:get_value(<<"conflicts">>, RowValueProps, [])),
- deleted_conflict_revs =
- couch_doc:parse_revs(proplists:get_value(<<"deleted_conflicts">>, RowValueProps, [])),
- deleted = proplists:get_value(<<"deleted">>, RowValueProps, false)
+ high_seq = Seq,
+ revs = Revs
}
end, proplists:get_value(<<"rows">>, Results));
get_doc_info_list(DbSource, StartSeq) ->
@@ -685,6 +680,7 @@ open_doc(Db, DocId, Options) ->
open_doc_revs(#http_db{uri=DbUrl, headers=Headers} = DbS, DocId, Revs0,
[latest]) ->
+ io:format("Revs0:~p~n", [Revs0]),
Revs = couch_doc:rev_to_strs(Revs0),
BaseUrl = DbUrl ++ url_encode(DocId) ++ "?revs=true&latest=true",
diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl
index 870d0d76..c1908629 100644
--- a/src/couchdb/couch_server.erl
+++ b/src/couchdb/couch_server.erl
@@ -183,8 +183,7 @@ maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
maybe_close_lru_db(#server{dbs_open=NumOpen}=Server) ->
% must free up the lru db.
case try_close_lru(now()) of
- ok ->
- couch_stats_collector:decrement({couchdb, open_databases}),
+ ok ->
{ok, Server#server{dbs_open=NumOpen - 1}};
Error -> Error
end.
@@ -238,7 +237,6 @@ handle_call({open, DbName, Options}, _From, Server) ->
true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
DbsOpen = Server2#server.dbs_open + 1,
- couch_stats_collector:increment({couchdb, open_databases}),
{reply, {ok, MainPid},
Server2#server{dbs_open=DbsOpen}};
Error ->
@@ -274,7 +272,6 @@ handle_call({create, DbName, Options}, _From, Server) ->
true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
DbsOpen = Server2#server.dbs_open + 1,
- couch_stats_collector:increment({couchdb, open_databases}),
couch_db_update_notifier:notify({created, DbName}),
{reply, {ok, MainPid},
Server2#server{dbs_open=DbsOpen}};
@@ -304,7 +301,6 @@ handle_call({delete, DbName, _Options}, _From, Server) ->
true = ets:delete(couch_dbs_by_name, DbName),
true = ets:delete(couch_dbs_by_pid, Pid),
true = ets:delete(couch_dbs_by_lru, LruTime),
- couch_stats_collector:decrement({couchdb, open_databases}),
Server#server{dbs_open=Server#server.dbs_open - 1}
end,
case file:delete(FullFilepath) of
@@ -334,7 +330,6 @@ handle_info({'EXIT', Pid, _Reason}, #server{dbs_open=DbsOpen}=Server) ->
true = ets:delete(couch_dbs_by_pid, Pid),
true = ets:delete(couch_dbs_by_name, DbName),
true = ets:delete(couch_dbs_by_lru, LruTime),
- couch_stats_collector:decrement({couchdb, open_databases}),
{noreply, Server#server{dbs_open=DbsOpen - 1}};
handle_info(Info, _Server) ->
exit({unknown_message, Info}).
diff --git a/src/couchdb/couch_stats_collector.erl b/src/couchdb/couch_stats_collector.erl
index 854fffb0..ad5e9e9a 100644
--- a/src/couchdb/couch_stats_collector.erl
+++ b/src/couchdb/couch_stats_collector.erl
@@ -24,6 +24,7 @@
-export([start/0, stop/0, get/1,
increment/1, decrement/1,
+ track_process_count/1,
record/2, clear/1,
all/0, all/1]).
@@ -87,6 +88,20 @@ all(Type) ->
end.
+track_process_count(Stat) ->
+ case (catch couch_stats_collector:increment(Stat)) of
+ ok ->
+ Self = self(),
+ spawn(
+ fun() ->
+ erlang:monitor(process, Self),
+ receive {'DOWN', _, _, _, _} -> ok end,
+ couch_stats_collector:decrement(Stat)
+ end);
+ _ -> ok
+ end.
+
+
% GEN_SERVER
diff --git a/src/couchdb/couch_view_updater.erl b/src/couchdb/couch_view_updater.erl
index 956ac3f1..c06e733e 100644
--- a/src/couchdb/couch_view_updater.erl
+++ b/src/couchdb/couch_view_updater.erl
@@ -92,15 +92,16 @@ purge_index(#group{db=Db, views=Views, id_btree=IdBtree}=Group) ->
process_doc(Db, DocInfo, {Docs, #group{sig=Sig,name=GroupId,design_options=DesignOptions}=Group, ViewKVs,
DocIdViewIdKeys}) ->
- % This fun computes once for each document
- #doc_info{id=DocId, deleted=Deleted} = DocInfo,
+ % This fun computes once for each document
+
+ #doc_info{id=DocId, revs=[#rev_info{deleted=Deleted}|_]} = DocInfo,
IncludeDesign = proplists:get_value(<<"include_design">>,
DesignOptions, false),
case {IncludeDesign, DocId} of
{_, GroupId} ->
% uh oh. this is the design doc with our definitions. See if
% anything in the definition changed.
- case couch_db:open_doc(Db, DocInfo, [conflicts, deleted_conflicts]) of
+ case couch_db:open_doc_int(Db, DocInfo, [conflicts, deleted_conflicts]) of
{ok, Doc} ->
case couch_view_group:design_doc_to_view_group(Doc) of
#group{sig=Sig} ->
@@ -124,7 +125,7 @@ process_doc(Db, DocInfo, {Docs, #group{sig=Sig,name=GroupId,design_options=Desig
if Deleted ->
{Docs, [{DocId, []} | DocIdViewIdKeys]};
true ->
- {ok, Doc} = couch_db:open_doc(Db, DocInfo,
+ {ok, Doc} = couch_db:open_doc_int(Db, DocInfo,
[conflicts, deleted_conflicts]),
{[Doc | Docs], DocIdViewIdKeys}
end,
@@ -135,7 +136,7 @@ process_doc(Db, DocInfo, {Docs, #group{sig=Sig,name=GroupId,design_options=Desig
{ViewKVs3, DocIdViewIdKeys3} = view_insert_query_results(Docs2,
Results, ViewKVs, DocIdViewIdKeys2),
{ok, Group2} = write_changes(Group1, ViewKVs3, DocIdViewIdKeys3,
- DocInfo#doc_info.update_seq),
+ DocInfo#doc_info.high_seq),
garbage_collect(),
ViewEmptyKeyValues = [{View, []} || View <- Group2#group.views],
{[], Group2, ViewEmptyKeyValues, []};