PageRenderTime 28ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/apps/couch/src/couch_db.erl

http://github.com/cloudant/bigcouch
Erlang | 1207 lines | 1004 code | 125 blank | 78 comment | 33 complexity | 7d696d89a53df37ad0882e0a3fa7d75c MD5 | raw file
Possible License(s): Apache-2.0
  1. % Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. % use this file except in compliance with the License. You may obtain a copy of
  3. % the License at
  4. %
  5. % http://www.apache.org/licenses/LICENSE-2.0
  6. %
  7. % Unless required by applicable law or agreed to in writing, software
  8. % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. % License for the specific language governing permissions and limitations under
  11. % the License.
  12. -module(couch_db).
  13. -export([open/2,open_int/2,close/1,create/2,start_compact/1,get_db_info/1,get_design_docs/1]).
  14. -export([open_ref_counted/2,is_idle/1,monitor/1,count_changes_since/2]).
  15. -export([update_doc/3,update_doc/4,update_docs/4,update_docs/2,update_docs/3,delete_doc/3]).
  16. -export([get_doc_info/2,open_doc/2,open_doc/3,open_doc_revs/4]).
  17. -export([set_revs_limit/2,get_revs_limit/1]).
  18. -export([get_missing_revs/2,name/1,doc_to_tree/1,get_update_seq/1,get_committed_update_seq/1]).
  19. -export([enum_docs/4,enum_docs_since/5]).
  20. -export([enum_docs_since_reduce_to_count/1,enum_docs_reduce_to_count/1]).
  21. -export([increment_update_seq/1,get_purge_seq/1,purge_docs/2,get_last_purged/1]).
  22. -export([start_link/3,open_doc_int/3,ensure_full_commit/1,ensure_full_commit/2]).
  23. -export([set_security/2,get_security/1]).
  24. -export([changes_since/4,changes_since/5,read_doc/2,new_revid/1]).
  25. -export([check_is_admin/1, check_is_reader/1, get_doc_count/1]).
  26. -export([reopen/1, make_doc/5]).
  27. -include("couch_db.hrl").
  28. start_link(DbName, Filepath, Options) ->
  29. case open_db_file(Filepath, Options) of
  30. {ok, Fd} ->
  31. {ok, UpdaterPid} = gen_server:start_link(couch_db_updater, {DbName,
  32. Filepath, Fd, Options}, []),
  33. unlink(Fd),
  34. gen_server:call(UpdaterPid, get_db);
  35. Else ->
  36. Else
  37. end.
  38. open_db_file(Filepath, Options) ->
  39. case couch_file:open(Filepath, Options) of
  40. {ok, Fd} ->
  41. {ok, Fd};
  42. {error, enoent} ->
  43. % couldn't find file. is there a compact version? This can happen if
  44. % crashed during the file switch.
  45. case couch_file:open(Filepath ++ ".compact") of
  46. {ok, Fd} ->
  47. ?LOG_INFO("Found ~s~s compaction file, using as primary storage.", [Filepath, ".compact"]),
  48. ok = file:rename(Filepath ++ ".compact", Filepath),
  49. ok = couch_file:sync(Filepath),
  50. {ok, Fd};
  51. {error, enoent} ->
  52. {not_found, no_db_file}
  53. end;
  54. Error ->
  55. Error
  56. end.
  57. create(DbName, Options) ->
  58. couch_server:create(DbName, Options).
  59. % this is for opening a database for internal purposes like the replicator
  60. % or the view indexer. it never throws a reader error.
  61. open_int(DbName, Options) ->
  62. couch_server:open(DbName, Options).
  63. % this should be called anytime an http request opens the database.
  64. % it ensures that the http userCtx is a valid reader
  65. open(DbName, Options) ->
  66. case couch_server:open(DbName, Options) of
  67. {ok, Db} ->
  68. try
  69. check_is_reader(Db),
  70. {ok, Db}
  71. catch
  72. throw:Error ->
  73. close(Db),
  74. throw(Error)
  75. end;
  76. Else -> Else
  77. end.
  78. reopen(#db{main_pid = Pid, fd = Fd, fd_monitor = OldRef, user_ctx = UserCtx}) ->
  79. {ok, #db{fd = NewFd} = NewDb} = gen_server:call(Pid, get_db, infinity),
  80. case NewFd =:= Fd of
  81. true ->
  82. {ok, NewDb#db{user_ctx = UserCtx}};
  83. false ->
  84. erlang:demonitor(OldRef),
  85. NewRef = erlang:monitor(process, NewFd),
  86. {ok, NewDb#db{user_ctx = UserCtx, fd_monitor = NewRef}}
  87. end.
  88. ensure_full_commit(#db{main_pid=Pid, instance_start_time=StartTime}) ->
  89. ok = gen_server:call(Pid, full_commit, infinity),
  90. {ok, StartTime}.
  91. ensure_full_commit(Db, RequiredSeq) ->
  92. #db{main_pid=Pid, instance_start_time=StartTime} = Db,
  93. ok = gen_server:call(Pid, {full_commit, RequiredSeq}, infinity),
  94. {ok, StartTime}.
  95. close(#db{fd_monitor=RefCntr}) ->
  96. erlang:demonitor(RefCntr).
  97. open_ref_counted(MainPid, OpenedPid) ->
  98. gen_server:call(MainPid, {open_ref_count, OpenedPid}).
  99. is_idle(#db{compactor_pid=nil, waiting_delayed_commit=nil} = Db) ->
  100. case erlang:process_info(Db#db.fd, monitored_by) of
  101. undefined ->
  102. true;
  103. {monitored_by, Pids} ->
  104. (Pids -- [Db#db.main_pid, whereis(couch_stats_collector)]) =:= []
  105. end;
  106. is_idle(_Db) ->
  107. false.
  108. monitor(#db{main_pid=MainPid}) ->
  109. erlang:monitor(process, MainPid).
  110. start_compact(#db{main_pid=Pid}) ->
  111. {ok, _} = gen_server:call(Pid, start_compact),
  112. ok.
  113. delete_doc(Db, Id, Revisions) ->
  114. DeletedDocs = [#doc{id=Id, revs=[Rev], deleted=true} || Rev <- Revisions],
  115. {ok, [Result]} = update_docs(Db, DeletedDocs, []),
  116. {ok, Result}.
  117. open_doc(Db, IdOrDocInfo) ->
  118. open_doc(Db, IdOrDocInfo, []).
  119. open_doc(Db, Id, Options) ->
  120. increment_stat(Db, {couchdb, database_reads}),
  121. case open_doc_int(Db, Id, Options) of
  122. {ok, #doc{deleted=true}=Doc} ->
  123. case lists:member(deleted, Options) of
  124. true ->
  125. apply_open_options({ok, Doc},Options);
  126. false ->
  127. {not_found, deleted}
  128. end;
  129. Else ->
  130. apply_open_options(Else,Options)
  131. end.
  132. apply_open_options({ok, Doc},Options) ->
  133. apply_open_options2(Doc,Options);
  134. apply_open_options(Else,_Options) ->
  135. Else.
  136. apply_open_options2(Doc,[]) ->
  137. {ok, Doc};
  138. apply_open_options2(#doc{atts=Atts,revs=Revs}=Doc,
  139. [{atts_since, PossibleAncestors}|Rest]) ->
  140. RevPos = find_ancestor_rev_pos(Revs, PossibleAncestors),
  141. apply_open_options2(Doc#doc{atts=[A#att{data=
  142. if AttPos>RevPos -> Data; true -> stub end}
  143. || #att{revpos=AttPos,data=Data}=A <- Atts]}, Rest);
  144. apply_open_options2(Doc,[_|Rest]) ->
  145. apply_open_options2(Doc,Rest).
  146. find_ancestor_rev_pos({_, []}, _AttsSinceRevs) ->
  147. 0;
  148. find_ancestor_rev_pos(_DocRevs, []) ->
  149. 0;
  150. find_ancestor_rev_pos({RevPos, [RevId|Rest]}, AttsSinceRevs) ->
  151. case lists:member({RevPos, RevId}, AttsSinceRevs) of
  152. true ->
  153. RevPos;
  154. false ->
  155. find_ancestor_rev_pos({RevPos - 1, Rest}, AttsSinceRevs)
  156. end.
  157. open_doc_revs(Db, Id, Revs, Options) ->
  158. increment_stat(Db, {couchdb, database_reads}),
  159. [{ok, Results}] = open_doc_revs_int(Db, [{Id, Revs}], Options),
  160. {ok, [apply_open_options(Result, Options) || Result <- Results]}.
  161. % Each returned result is a list of tuples:
  162. % {Id, MissingRevs, PossibleAncestors}
  163. % if no revs are missing, it's omitted from the results.
  164. get_missing_revs(Db, IdRevsList) ->
  165. Results = get_full_doc_infos(Db, [Id1 || {Id1, _Revs} <- IdRevsList]),
  166. {ok, find_missing(IdRevsList, Results)}.
  167. find_missing([], []) ->
  168. [];
  169. find_missing([{Id, Revs}|RestIdRevs], [{ok, FullInfo} | RestLookupInfo]) ->
  170. case couch_key_tree:find_missing(FullInfo#full_doc_info.rev_tree, Revs) of
  171. [] ->
  172. find_missing(RestIdRevs, RestLookupInfo);
  173. MissingRevs ->
  174. #doc_info{revs=RevsInfo} = couch_doc:to_doc_info(FullInfo),
  175. LeafRevs = [Rev || #rev_info{rev=Rev} <- RevsInfo],
  176. % Find the revs that are possible parents of this rev
  177. PossibleAncestors =
  178. lists:foldl(fun({LeafPos, LeafRevId}, Acc) ->
  179. % this leaf is a "possible ancenstor" of the missing
  180. % revs if this LeafPos lessthan any of the missing revs
  181. case lists:any(fun({MissingPos, _}) ->
  182. LeafPos < MissingPos end, MissingRevs) of
  183. true ->
  184. [{LeafPos, LeafRevId} | Acc];
  185. false ->
  186. Acc
  187. end
  188. end, [], LeafRevs),
  189. [{Id, MissingRevs, PossibleAncestors} |
  190. find_missing(RestIdRevs, RestLookupInfo)]
  191. end;
  192. find_missing([{Id, Revs}|RestIdRevs], [not_found | RestLookupInfo]) ->
  193. [{Id, Revs, []} | find_missing(RestIdRevs, RestLookupInfo)].
  194. get_doc_info(Db, Id) ->
  195. case get_full_doc_info(Db, Id) of
  196. {ok, DocInfo} ->
  197. {ok, couch_doc:to_doc_info(DocInfo)};
  198. Else ->
  199. Else
  200. end.
  201. % returns {ok, DocInfo} or not_found
  202. get_full_doc_info(Db, Id) ->
  203. [Result] = get_full_doc_infos(Db, [Id]),
  204. Result.
  205. get_full_doc_infos(Db, Ids) ->
  206. couch_btree:lookup(Db#db.id_tree, Ids).
  207. increment_update_seq(#db{main_pid=Pid}) ->
  208. gen_server:call(Pid, increment_update_seq).
  209. purge_docs(#db{main_pid=Pid}, IdsRevs) ->
  210. gen_server:call(Pid, {purge_docs, IdsRevs}).
  211. get_committed_update_seq(#db{committed_update_seq=Seq}) ->
  212. Seq.
  213. get_update_seq(#db{update_seq=Seq})->
  214. Seq.
  215. get_purge_seq(#db{header=#db_header{purge_seq=PurgeSeq}})->
  216. PurgeSeq.
  217. get_last_purged(#db{header=#db_header{purged_docs=nil}}) ->
  218. {ok, []};
  219. get_last_purged(#db{fd=Fd, header=#db_header{purged_docs=PurgedPointer}}) ->
  220. couch_file:pread_term(Fd, PurgedPointer).
  221. get_doc_count(Db) ->
  222. {ok, {Count, _, _}} = couch_btree:full_reduce(Db#db.id_tree),
  223. {ok, Count}.
  224. get_db_info(Db) ->
  225. #db{fd=Fd,
  226. header=#db_header{disk_version=DiskVersion},
  227. compactor_pid=Compactor,
  228. update_seq=SeqNum,
  229. name=Name,
  230. id_tree=FullDocBtree,
  231. instance_start_time=StartTime,
  232. committed_update_seq=CommittedUpdateSeq} = Db,
  233. {ok, Size} = couch_file:bytes(Fd),
  234. {ok, {Count, DelCount, DataSize}} = couch_btree:full_reduce(FullDocBtree),
  235. InfoList = [
  236. {db_name, Name},
  237. {doc_count, Count},
  238. {doc_del_count, DelCount},
  239. {update_seq, SeqNum},
  240. {purge_seq, couch_db:get_purge_seq(Db)},
  241. {compact_running, Compactor/=nil},
  242. {disk_size, Size},
  243. {other, {[{data_size, DataSize}]}},
  244. {instance_start_time, StartTime},
  245. {disk_format_version, DiskVersion},
  246. {committed_update_seq, CommittedUpdateSeq}
  247. ],
  248. {ok, InfoList}.
  249. get_design_docs(#db{name = <<"shards/", _/binary>> = ShardName}) ->
  250. {_, Ref} = spawn_monitor(fun() ->
  251. exit(fabric:design_docs(mem3:dbname(ShardName)))
  252. end),
  253. receive {'DOWN', Ref, _, _, Response} ->
  254. Response
  255. end;
  256. get_design_docs(#db{id_tree=Btree}=Db) ->
  257. {ok, _, Docs} = couch_view:fold(
  258. #view{btree=Btree},
  259. fun(#full_doc_info{deleted = true}, _Reds, AccDocs) ->
  260. {ok, AccDocs};
  261. (#full_doc_info{id= <<"_design/",_/binary>>}=FullDocInfo, _Reds, AccDocs) ->
  262. {ok, Doc} = couch_db:open_doc_int(Db, FullDocInfo, []),
  263. {ok, [Doc | AccDocs]};
  264. (_, _Reds, AccDocs) ->
  265. {stop, AccDocs}
  266. end,
  267. [], [{start_key, <<"_design/">>}, {end_key_gt, <<"_design0">>}]),
  268. {ok, Docs}.
  269. check_is_admin(#db{user_ctx=#user_ctx{name=Name,roles=Roles}}=Db) ->
  270. {Admins} = get_admins(Db),
  271. AdminRoles = [<<"_admin">> | couch_util:get_value(<<"roles">>, Admins, [])],
  272. AdminNames = couch_util:get_value(<<"names">>, Admins,[]),
  273. case AdminRoles -- Roles of
  274. AdminRoles -> % same list, not an admin role
  275. case AdminNames -- [Name] of
  276. AdminNames -> % same names, not an admin
  277. throw({unauthorized, <<"You are not a db or server admin.">>});
  278. _ ->
  279. ok
  280. end;
  281. _ ->
  282. ok
  283. end.
  284. check_is_reader(#db{user_ctx=#user_ctx{name=Name,roles=Roles}=UserCtx}=Db) ->
  285. case (catch check_is_admin(Db)) of
  286. ok -> ok;
  287. _ ->
  288. {Readers} = get_readers(Db),
  289. ReaderRoles = couch_util:get_value(<<"roles">>, Readers,[]),
  290. WithAdminRoles = [<<"_admin">> | ReaderRoles],
  291. ReaderNames = couch_util:get_value(<<"names">>, Readers,[]),
  292. case ReaderRoles ++ ReaderNames of
  293. [] -> ok; % no readers == public access
  294. _Else ->
  295. case WithAdminRoles -- Roles of
  296. WithAdminRoles -> % same list, not an reader role
  297. case ReaderNames -- [Name] of
  298. ReaderNames -> % same names, not a reader
  299. ?LOG_DEBUG("Not a reader: UserCtx ~p vs Names ~p Roles ~p",[UserCtx, ReaderNames, WithAdminRoles]),
  300. throw({unauthorized, <<"You are not authorized to access this db.">>});
  301. _ ->
  302. ok
  303. end;
  304. _ ->
  305. ok
  306. end
  307. end
  308. end.
  309. get_admins(#db{security=SecProps}) ->
  310. couch_util:get_value(<<"admins">>, SecProps, {[]}).
  311. get_readers(#db{security=SecProps}) ->
  312. couch_util:get_value(<<"readers">>, SecProps, {[]}).
  313. get_security(#db{security=SecProps}) ->
  314. {SecProps}.
  315. set_security(#db{main_pid=Pid}=Db, {NewSecProps}) when is_list(NewSecProps) ->
  316. check_is_admin(Db),
  317. ok = validate_security_object(NewSecProps),
  318. ok = gen_server:call(Pid, {set_security, NewSecProps}, infinity),
  319. {ok, _} = ensure_full_commit(Db),
  320. ok;
  321. set_security(_, _) ->
  322. throw(bad_request).
  323. validate_security_object(SecProps) ->
  324. Admins = couch_util:get_value(<<"admins">>, SecProps, {[]}),
  325. Readers = couch_util:get_value(<<"readers">>, SecProps, {[]}),
  326. ok = validate_names_and_roles(Admins),
  327. ok = validate_names_and_roles(Readers),
  328. ok.
  329. % validate user input
  330. validate_names_and_roles({Props}) when is_list(Props) ->
  331. case couch_util:get_value(<<"names">>,Props,[]) of
  332. Ns when is_list(Ns) ->
  333. [throw("names must be a JSON list of strings") ||N <- Ns, not is_binary(N)],
  334. Ns;
  335. _ -> throw("names must be a JSON list of strings")
  336. end,
  337. case couch_util:get_value(<<"roles">>,Props,[]) of
  338. Rs when is_list(Rs) ->
  339. [throw("roles must be a JSON list of strings") ||R <- Rs, not is_binary(R)],
  340. Rs;
  341. _ -> throw("roles must be a JSON list of strings")
  342. end,
  343. ok.
  344. get_revs_limit(#db{revs_limit=Limit}) ->
  345. Limit.
  346. set_revs_limit(#db{main_pid=Pid}=Db, Limit) when Limit > 0 ->
  347. check_is_admin(Db),
  348. gen_server:call(Pid, {set_revs_limit, Limit}, infinity);
  349. set_revs_limit(_Db, _Limit) ->
  350. throw(invalid_revs_limit).
  351. name(#db{name=Name}) ->
  352. Name.
  353. update_doc(Db, Doc, Options) ->
  354. update_doc(Db, Doc, Options, interactive_edit).
  355. update_doc(Db, Doc, Options, UpdateType) ->
  356. case update_docs(Db, [Doc], Options, UpdateType) of
  357. {ok, [{ok, NewRev}]} ->
  358. {ok, NewRev};
  359. {ok, [{{_Id, _Rev}, Error}]} ->
  360. throw(Error);
  361. {ok, [Error]} ->
  362. throw(Error);
  363. {ok, []} ->
  364. % replication success
  365. {Pos, [RevId | _]} = Doc#doc.revs,
  366. {ok, {Pos, RevId}}
  367. end.
  368. update_docs(Db, Docs) ->
  369. update_docs(Db, Docs, []).
  370. % group_alike_docs groups the sorted documents into sublist buckets, by id.
  371. % ([DocA, DocA, DocB, DocC], []) -> [[DocA, DocA], [DocB], [DocC]]
  372. group_alike_docs(Docs) ->
  373. Sorted = lists:sort(fun(#doc{id=A},#doc{id=B})-> A < B end, Docs),
  374. group_alike_docs(Sorted, []).
  375. group_alike_docs([], Buckets) ->
  376. lists:reverse(Buckets);
  377. group_alike_docs([Doc|Rest], []) ->
  378. group_alike_docs(Rest, [[Doc]]);
  379. group_alike_docs([Doc|Rest], [Bucket|RestBuckets]) ->
  380. [#doc{id=BucketId}|_] = Bucket,
  381. case Doc#doc.id == BucketId of
  382. true ->
  383. % add to existing bucket
  384. group_alike_docs(Rest, [[Doc|Bucket]|RestBuckets]);
  385. false ->
  386. % add to new bucket
  387. group_alike_docs(Rest, [[Doc]|[Bucket|RestBuckets]])
  388. end.
  389. validate_doc_update(#db{}=Db, #doc{id= <<"_design/",_/binary>>}, _GetDiskDocFun) ->
  390. catch check_is_admin(Db);
  391. validate_doc_update(#db{validate_doc_funs = undefined} = Db, Doc, Fun) ->
  392. ValidationFuns = load_validation_funs(Db),
  393. validate_doc_update(Db#db{validate_doc_funs = ValidationFuns}, Doc, Fun);
  394. validate_doc_update(#db{validate_doc_funs=[]}, _Doc, _GetDiskDocFun) ->
  395. ok;
  396. validate_doc_update(_Db, #doc{id= <<"_local/",_/binary>>}, _GetDiskDocFun) ->
  397. ok;
  398. validate_doc_update(Db, Doc, GetDiskDocFun) ->
  399. DiskDoc = GetDiskDocFun(),
  400. JsonCtx = couch_util:json_user_ctx(Db),
  401. SecObj = get_security(Db),
  402. try [case Fun(Doc, DiskDoc, JsonCtx, SecObj) of
  403. ok -> ok;
  404. Error -> throw(Error)
  405. end || Fun <- Db#db.validate_doc_funs],
  406. ok
  407. catch
  408. throw:Error ->
  409. Error
  410. end.
  411. % to be safe, spawn a middleman here
  412. load_validation_funs(#db{main_pid = Pid} = Db) ->
  413. {_, Ref} = spawn_monitor(fun() ->
  414. {ok, DesignDocs} = get_design_docs(Db),
  415. exit({ok, lists:flatmap(fun(DesignDoc) ->
  416. case couch_doc:get_validate_doc_fun(DesignDoc) of
  417. nil ->
  418. [];
  419. Fun ->
  420. [Fun]
  421. end
  422. end, DesignDocs)})
  423. end),
  424. receive
  425. {'DOWN', Ref, _, _, {ok, Funs}} ->
  426. gen_server:cast(Pid, {load_validation_funs, Funs}),
  427. Funs;
  428. {'DOWN', Ref, _, _, Reason} ->
  429. ?LOG_ERROR("could not load validation funs ~p", [Reason]),
  430. throw(internal_server_error)
  431. end.
  432. prep_and_validate_update(Db, #doc{id=Id,revs={RevStart, Revs}}=Doc,
  433. OldFullDocInfo, LeafRevsDict, AllowConflict) ->
  434. case Revs of
  435. [PrevRev|_] ->
  436. case dict:find({RevStart, PrevRev}, LeafRevsDict) of
  437. {ok, {Deleted, DiskSp, DiskRevs}} ->
  438. case couch_doc:has_stubs(Doc) of
  439. true ->
  440. DiskDoc = make_doc(Db, Id, Deleted, DiskSp, DiskRevs),
  441. Doc2 = couch_doc:merge_stubs(Doc, DiskDoc),
  442. {validate_doc_update(Db, Doc2, fun() -> DiskDoc end), Doc2};
  443. false ->
  444. LoadDiskDoc = fun() -> make_doc(Db,Id,Deleted,DiskSp,DiskRevs) end,
  445. {validate_doc_update(Db, Doc, LoadDiskDoc), Doc}
  446. end;
  447. error when AllowConflict ->
  448. couch_doc:merge_stubs(Doc, #doc{}), % will generate error if
  449. % there are stubs
  450. {validate_doc_update(Db, Doc, fun() -> nil end), Doc};
  451. error ->
  452. {conflict, Doc}
  453. end;
  454. [] ->
  455. % new doc, and we have existing revs.
  456. % reuse existing deleted doc
  457. if OldFullDocInfo#full_doc_info.deleted orelse AllowConflict ->
  458. {validate_doc_update(Db, Doc, fun() -> nil end), Doc};
  459. true ->
  460. {conflict, Doc}
  461. end
  462. end.
  463. prep_and_validate_updates(_Db, [], [], _AllowConflict, AccPrepped,
  464. AccFatalErrors) ->
  465. {AccPrepped, AccFatalErrors};
  466. prep_and_validate_updates(Db, [DocBucket|RestBuckets], [not_found|RestLookups],
  467. AllowConflict, AccPrepped, AccErrors) ->
  468. [#doc{id=Id}|_]=DocBucket,
  469. % no existing revs are known,
  470. {PreppedBucket, AccErrors3} = lists:foldl(
  471. fun(#doc{revs=Revs}=Doc, {AccBucket, AccErrors2}) ->
  472. case couch_doc:has_stubs(Doc) of
  473. true ->
  474. couch_doc:merge_stubs(Doc, #doc{}); % will throw exception
  475. false -> ok
  476. end,
  477. case Revs of
  478. {0, []} ->
  479. case validate_doc_update(Db, Doc, fun() -> nil end) of
  480. ok ->
  481. {[Doc | AccBucket], AccErrors2};
  482. Error ->
  483. {AccBucket, [{{Id, {0, []}}, Error} | AccErrors2]}
  484. end;
  485. _ ->
  486. % old revs specified but none exist, a conflict
  487. {AccBucket, [{{Id, Revs}, conflict} | AccErrors2]}
  488. end
  489. end,
  490. {[], AccErrors}, DocBucket),
  491. prep_and_validate_updates(Db, RestBuckets, RestLookups, AllowConflict,
  492. [PreppedBucket | AccPrepped], AccErrors3);
  493. prep_and_validate_updates(Db, [DocBucket|RestBuckets],
  494. [{ok, #full_doc_info{rev_tree=OldRevTree}=OldFullDocInfo}|RestLookups],
  495. AllowConflict, AccPrepped, AccErrors) ->
  496. Leafs = couch_key_tree:get_all_leafs(OldRevTree),
  497. LeafRevsDict = dict:from_list([{{Start, RevId}, {Del, Ptr, Revs}} ||
  498. {#leaf{deleted=Del, ptr=Ptr}, {Start, [RevId|_]}=Revs} <- Leafs]),
  499. {PreppedBucket, AccErrors3} = lists:foldl(
  500. fun(Doc, {Docs2Acc, AccErrors2}) ->
  501. case prep_and_validate_update(Db, Doc, OldFullDocInfo,
  502. LeafRevsDict, AllowConflict) of
  503. {ok, Doc2} ->
  504. {[Doc2 | Docs2Acc], AccErrors2};
  505. {Error, #doc{id=Id,revs=Revs}} ->
  506. % Record the error
  507. {Docs2Acc, [{{Id, Revs}, Error} |AccErrors2]}
  508. end
  509. end,
  510. {[], AccErrors}, DocBucket),
  511. prep_and_validate_updates(Db, RestBuckets, RestLookups, AllowConflict,
  512. [PreppedBucket | AccPrepped], AccErrors3).
  513. update_docs(Db, Docs, Options) ->
  514. update_docs(Db, Docs, Options, interactive_edit).
  515. prep_and_validate_replicated_updates(_Db, [], [], AccPrepped, AccErrors) ->
  516. Errors2 = [{{Id, {Pos, Rev}}, Error} ||
  517. {#doc{id=Id,revs={Pos,[Rev|_]}}, Error} <- AccErrors],
  518. {lists:reverse(AccPrepped), lists:reverse(Errors2)};
  519. prep_and_validate_replicated_updates(Db, [Bucket|RestBuckets], [OldInfo|RestOldInfo], AccPrepped, AccErrors) ->
  520. case OldInfo of
  521. not_found ->
  522. {ValidatedBucket, AccErrors3} = lists:foldl(
  523. fun(Doc, {AccPrepped2, AccErrors2}) ->
  524. case couch_doc:has_stubs(Doc) of
  525. true ->
  526. couch_doc:merge_stubs(Doc, #doc{}); % will throw exception
  527. false -> ok
  528. end,
  529. case validate_doc_update(Db, Doc, fun() -> nil end) of
  530. ok ->
  531. {[Doc | AccPrepped2], AccErrors2};
  532. Error ->
  533. {AccPrepped2, [{Doc, Error} | AccErrors2]}
  534. end
  535. end,
  536. {[], AccErrors}, Bucket),
  537. prep_and_validate_replicated_updates(Db, RestBuckets, RestOldInfo, [ValidatedBucket | AccPrepped], AccErrors3);
  538. {ok, #full_doc_info{rev_tree=OldTree}} ->
  539. NewRevTree = lists:foldl(
  540. fun(NewDoc, AccTree) ->
  541. {NewTree, _} = couch_key_tree:merge(AccTree,
  542. couch_db:doc_to_tree(NewDoc), Db#db.revs_limit),
  543. NewTree
  544. end,
  545. OldTree, Bucket),
  546. Leafs = couch_key_tree:get_all_leafs_full(NewRevTree),
  547. LeafRevsFullDict = dict:from_list( [{{Start, RevId}, FullPath} || {Start, [{RevId, _}|_]}=FullPath <- Leafs]),
  548. {ValidatedBucket, AccErrors3} =
  549. lists:foldl(
  550. fun(#doc{id=Id,revs={Pos, [RevId|_]}}=Doc, {AccValidated, AccErrors2}) ->
  551. case dict:find({Pos, RevId}, LeafRevsFullDict) of
  552. {ok, {Start, Path}} ->
  553. % our unflushed doc is a leaf node. Go back on the path
  554. % to find the previous rev that's on disk.
  555. LoadPrevRevFun = fun() ->
  556. make_first_doc_on_disk(Db,Id,Start-1, tl(Path))
  557. end,
  558. case couch_doc:has_stubs(Doc) of
  559. true ->
  560. DiskDoc = LoadPrevRevFun(),
  561. Doc2 = couch_doc:merge_stubs(Doc, DiskDoc),
  562. GetDiskDocFun = fun() -> DiskDoc end;
  563. false ->
  564. Doc2 = Doc,
  565. GetDiskDocFun = LoadPrevRevFun
  566. end,
  567. case validate_doc_update(Db, Doc2, GetDiskDocFun) of
  568. ok ->
  569. {[Doc2 | AccValidated], AccErrors2};
  570. Error ->
  571. {AccValidated, [{Doc, Error} | AccErrors2]}
  572. end;
  573. _ ->
  574. % this doc isn't a leaf or already exists in the tree.
  575. % ignore but consider it a success.
  576. {AccValidated, AccErrors2}
  577. end
  578. end,
  579. {[], AccErrors}, Bucket),
  580. prep_and_validate_replicated_updates(Db, RestBuckets, RestOldInfo,
  581. [ValidatedBucket | AccPrepped], AccErrors3)
  582. end.
  583. new_revid(#doc{body=Body,revs={OldStart,OldRevs},
  584. atts=Atts,deleted=Deleted}) ->
  585. case [{N, T, M} || #att{name=N,type=T,md5=M} <- Atts, M =/= <<>>] of
  586. Atts2 when length(Atts) =/= length(Atts2) ->
  587. % We must have old style non-md5 attachments
  588. ?l2b(integer_to_list(couch_util:rand32()));
  589. Atts2 ->
  590. OldRev = case OldRevs of [] -> 0; [OldRev0|_] -> OldRev0 end,
  591. couch_util:md5(term_to_binary([Deleted, OldStart, OldRev, Body, Atts2]))
  592. end.
  593. new_revs([], OutBuckets, IdRevsAcc) ->
  594. {lists:reverse(OutBuckets), IdRevsAcc};
  595. new_revs([Bucket|RestBuckets], OutBuckets, IdRevsAcc) ->
  596. {NewBucket, IdRevsAcc3} = lists:mapfoldl(
  597. fun(#doc{id=Id,revs={Start, RevIds}}=Doc, IdRevsAcc2)->
  598. NewRevId = new_revid(Doc),
  599. {Doc#doc{revs={Start+1, [NewRevId | RevIds]}},
  600. [{{Id, {Start, RevIds}}, {ok, {Start+1, NewRevId}}} | IdRevsAcc2]}
  601. end, IdRevsAcc, Bucket),
  602. new_revs(RestBuckets, [NewBucket|OutBuckets], IdRevsAcc3).
  603. check_dup_atts(#doc{atts=Atts}=Doc) ->
  604. Atts2 = lists:sort(fun(#att{name=N1}, #att{name=N2}) -> N1 < N2 end, Atts),
  605. check_dup_atts2(Atts2),
  606. Doc.
  607. check_dup_atts2([#att{name=N}, #att{name=N} | _]) ->
  608. throw({bad_request, <<"Duplicate attachments">>});
  609. check_dup_atts2([_ | Rest]) ->
  610. check_dup_atts2(Rest);
  611. check_dup_atts2(_) ->
  612. ok.
  613. update_docs(Db, Docs, Options, replicated_changes) ->
  614. increment_stat(Db, {couchdb, database_writes}),
  615. DocBuckets = group_alike_docs(Docs),
  616. case (Db#db.validate_doc_funs /= []) orelse
  617. lists:any(
  618. fun(#doc{id= <<?DESIGN_DOC_PREFIX, _/binary>>}) -> true;
  619. (#doc{atts=Atts}) ->
  620. Atts /= []
  621. end, Docs) of
  622. true ->
  623. Ids = [Id || [#doc{id=Id}|_] <- DocBuckets],
  624. ExistingDocs = get_full_doc_infos(Db, Ids),
  625. {DocBuckets2, DocErrors} =
  626. prep_and_validate_replicated_updates(Db, DocBuckets, ExistingDocs, [], []),
  627. DocBuckets3 = [Bucket || [_|_]=Bucket <- DocBuckets2]; % remove empty buckets
  628. false ->
  629. DocErrors = [],
  630. DocBuckets3 = DocBuckets
  631. end,
  632. DocBuckets4 = [[doc_flush_atts(check_dup_atts(Doc), Db#db.fd)
  633. || Doc <- Bucket] || Bucket <- DocBuckets3],
  634. {ok, []} = write_and_commit(Db, DocBuckets4, [], [merge_conflicts | Options]),
  635. {ok, DocErrors};
  636. update_docs(Db, Docs, Options, interactive_edit) ->
  637. increment_stat(Db, {couchdb, database_writes}),
  638. AllOrNothing = lists:member(all_or_nothing, Options),
  639. % go ahead and generate the new revision ids for the documents.
  640. % separate out the NonRep documents from the rest of the documents
  641. {Docs2, NonRepDocs} = lists:foldl(
  642. fun(#doc{id=Id}=Doc, {DocsAcc, NonRepDocsAcc}) ->
  643. case Id of
  644. <<?LOCAL_DOC_PREFIX, _/binary>> ->
  645. {DocsAcc, [Doc | NonRepDocsAcc]};
  646. Id->
  647. {[Doc | DocsAcc], NonRepDocsAcc}
  648. end
  649. end, {[], []}, Docs),
  650. DocBuckets = group_alike_docs(Docs2),
  651. case (Db#db.validate_doc_funs /= []) orelse
  652. lists:any(
  653. fun(#doc{id= <<?DESIGN_DOC_PREFIX, _/binary>>}) ->
  654. true;
  655. (#doc{atts=Atts}) ->
  656. Atts /= []
  657. end, Docs2) of
  658. true ->
  659. % lookup the doc by id and get the most recent
  660. Ids = [Id || [#doc{id=Id}|_] <- DocBuckets],
  661. ExistingDocInfos = get_full_doc_infos(Db, Ids),
  662. {DocBucketsPrepped, PreCommitFailures} = prep_and_validate_updates(Db,
  663. DocBuckets, ExistingDocInfos, AllOrNothing, [], []),
  664. % strip out any empty buckets
  665. DocBuckets2 = [Bucket || [_|_] = Bucket <- DocBucketsPrepped];
  666. false ->
  667. PreCommitFailures = [],
  668. DocBuckets2 = DocBuckets
  669. end,
  670. if (AllOrNothing) and (PreCommitFailures /= []) ->
  671. {aborted, lists:map(
  672. fun({{Id,{Pos, [RevId|_]}}, Error}) ->
  673. {{Id, {Pos, RevId}}, Error};
  674. ({{Id,{0, []}}, Error}) ->
  675. {{Id, {0, <<>>}}, Error}
  676. end, PreCommitFailures)};
  677. true ->
  678. Options2 = if AllOrNothing -> [merge_conflicts];
  679. true -> [] end ++ Options,
  680. DocBuckets3 = [[
  681. doc_flush_atts(set_new_att_revpos(
  682. check_dup_atts(Doc)), Db#db.fd)
  683. || Doc <- B] || B <- DocBuckets2],
  684. {DocBuckets4, IdRevs} = new_revs(DocBuckets3, [], []),
  685. {ok, CommitResults} = write_and_commit(Db, DocBuckets4, NonRepDocs, Options2),
  686. ResultsDict = dict:from_list(IdRevs ++ CommitResults ++ PreCommitFailures),
  687. {ok, lists:map(
  688. fun(#doc{id=Id,revs={Pos, RevIds}}) ->
  689. {ok, Result} = dict:find({Id, {Pos, RevIds}}, ResultsDict),
  690. Result
  691. end, Docs)}
  692. end.
  693. % Returns the first available document on disk. Input list is a full rev path
  694. % for the doc.
  695. make_first_doc_on_disk(_Db, _Id, _Pos, []) ->
  696. nil;
  697. make_first_doc_on_disk(Db, Id, Pos, [{_Rev, #doc{}} | RestPath]) ->
  698. make_first_doc_on_disk(Db, Id, Pos-1, RestPath);
  699. make_first_doc_on_disk(Db, Id, Pos, [{_Rev, ?REV_MISSING}|RestPath]) ->
  700. make_first_doc_on_disk(Db, Id, Pos - 1, RestPath);
  701. make_first_doc_on_disk(Db, Id, Pos, [{_, #leaf{deleted=IsDel, ptr=Sp}} |_]=DocPath) ->
  702. Revs = [Rev || {Rev, _} <- DocPath],
  703. make_doc(Db, Id, IsDel, Sp, {Pos, Revs}).
  704. set_commit_option(Options) ->
  705. CommitSettings = {
  706. [true || O <- Options, O==full_commit orelse O==delay_commit],
  707. couch_config:get("couchdb", "delayed_commits", "false")
  708. },
  709. case CommitSettings of
  710. {[true], _} ->
  711. Options; % user requested explicit commit setting, do not change it
  712. {_, "true"} ->
  713. Options; % delayed commits are enabled, do nothing
  714. {_, "false"} ->
  715. [full_commit|Options];
  716. {_, Else} ->
  717. ?LOG_ERROR("[couchdb] delayed_commits setting must be true/false, not ~p",
  718. [Else]),
  719. [full_commit|Options]
  720. end.
  721. collect_results(Pid, MRef, ResultsAcc) ->
  722. receive
  723. {result, Pid, Result} ->
  724. collect_results(Pid, MRef, [Result | ResultsAcc]);
  725. {done, Pid} ->
  726. {ok, ResultsAcc};
  727. {retry, Pid} ->
  728. retry;
  729. {'DOWN', MRef, _, _, Reason} ->
  730. exit(Reason)
  731. end.
  732. write_and_commit(#db{main_pid=Pid, user_ctx=Ctx}=Db, DocBuckets,
  733. NonRepDocs, Options0) ->
  734. Options = set_commit_option(Options0),
  735. MergeConflicts = lists:member(merge_conflicts, Options),
  736. FullCommit = lists:member(full_commit, Options),
  737. MRef = erlang:monitor(process, Pid),
  738. try
  739. Pid ! {update_docs, self(), DocBuckets, NonRepDocs, MergeConflicts, FullCommit},
  740. case collect_results(Pid, MRef, []) of
  741. {ok, Results} -> {ok, Results};
  742. retry ->
  743. % This can happen if the db file we wrote to was swapped out by
  744. % compaction. Retry by reopening the db and writing to the current file
  745. {ok, Db2} = open(Db#db.name, [{user_ctx, Ctx}]),
  746. DocBuckets2 = [[doc_flush_atts(Doc, Db2#db.fd) || Doc <- Bucket] || Bucket <- DocBuckets],
  747. % We only retry once
  748. close(Db2),
  749. Pid ! {update_docs, self(), DocBuckets2, NonRepDocs, MergeConflicts, FullCommit},
  750. case collect_results(Pid, MRef, []) of
  751. {ok, Results} -> {ok, Results};
  752. retry -> throw({update_error, compaction_retry})
  753. end
  754. end
  755. after
  756. erlang:demonitor(MRef, [flush])
  757. end.
  758. set_new_att_revpos(#doc{revs={RevPos,_Revs},atts=Atts}=Doc) ->
  759. Doc#doc{atts= lists:map(fun(#att{data={_Fd,_Sp}}=Att) ->
  760. % already commited to disk, do not set new rev
  761. Att;
  762. (Att) ->
  763. Att#att{revpos=RevPos+1}
  764. end, Atts)}.
  765. doc_flush_atts(Doc, Fd) ->
  766. Doc#doc{atts=[flush_att(Fd, Att) || Att <- Doc#doc.atts]}.
  767. check_md5(_NewSig, <<>>) -> ok;
  768. check_md5(Sig, Sig) -> ok;
  769. check_md5(_, _) -> throw(md5_mismatch).
  770. flush_att(Fd, #att{data={Fd0, _}}=Att) when Fd0 == Fd ->
  771. % already written to our file, nothing to write
  772. Att;
  773. flush_att(Fd, #att{data={OtherFd,StreamPointer}, md5=InMd5,
  774. disk_len=InDiskLen} = Att) ->
  775. {NewStreamData, Len, _IdentityLen, Md5, IdentityMd5} =
  776. couch_stream:copy_to_new_stream(OtherFd, StreamPointer, Fd),
  777. check_md5(IdentityMd5, InMd5),
  778. Att#att{data={Fd, NewStreamData}, md5=Md5, att_len=Len, disk_len=InDiskLen};
  779. flush_att(Fd, #att{data=Data}=Att) when is_binary(Data) ->
  780. with_stream(Fd, Att, fun(OutputStream) ->
  781. couch_stream:write(OutputStream, Data)
  782. end);
  783. flush_att(Fd, #att{data=Fun,att_len=undefined}=Att) when is_function(Fun) ->
  784. with_stream(Fd, Att, fun(OutputStream) ->
  785. % Fun(MaxChunkSize, WriterFun) must call WriterFun
  786. % once for each chunk of the attachment,
  787. Fun(4096,
  788. % WriterFun({Length, Binary}, State)
  789. % WriterFun({0, _Footers}, State)
  790. % Called with Length == 0 on the last time.
  791. % WriterFun returns NewState.
  792. fun({0, Footers}, _) ->
  793. F = mochiweb_headers:from_binary(Footers),
  794. case mochiweb_headers:get_value("Content-MD5", F) of
  795. undefined ->
  796. ok;
  797. Md5 ->
  798. {md5, base64:decode(Md5)}
  799. end;
  800. ({_Length, Chunk}, _) ->
  801. couch_stream:write(OutputStream, Chunk)
  802. end, ok)
  803. end);
  804. flush_att(Fd, #att{data=Fun,att_len=AttLen}=Att) when is_function(Fun) ->
  805. with_stream(Fd, Att, fun(OutputStream) ->
  806. write_streamed_attachment(OutputStream, Fun, AttLen)
  807. end).
  808. % From RFC 2616 3.6.1 - Chunked Transfer Coding
  809. %
  810. % In other words, the origin server is willing to accept
  811. % the possibility that the trailer fields might be silently
  812. % discarded along the path to the client.
  813. %
  814. % I take this to mean that if "Trailers: Content-MD5\r\n"
  815. % is present in the request, but there is no Content-MD5
  816. % trailer, we're free to ignore this inconsistency and
  817. % pretend that no Content-MD5 exists.
  818. with_stream(Fd, #att{md5=InMd5,type=Type,encoding=Enc}=Att, Fun) ->
  819. {ok, OutputStream} = case (Enc =:= identity) andalso
  820. couch_util:compressible_att_type(Type) of
  821. true ->
  822. CompLevel = list_to_integer(
  823. couch_config:get("attachments", "compression_level", "0")
  824. ),
  825. couch_stream:open(Fd, gzip, [{compression_level, CompLevel}]);
  826. _ ->
  827. couch_stream:open(Fd)
  828. end,
  829. ReqMd5 = case Fun(OutputStream) of
  830. {md5, FooterMd5} ->
  831. case InMd5 of
  832. md5_in_footer -> FooterMd5;
  833. _ -> InMd5
  834. end;
  835. _ ->
  836. InMd5
  837. end,
  838. {StreamInfo, Len, IdentityLen, Md5, IdentityMd5} =
  839. couch_stream:close(OutputStream),
  840. check_md5(IdentityMd5, ReqMd5),
  841. {AttLen, DiskLen, NewEnc} = case Enc of
  842. identity ->
  843. case {Md5, IdentityMd5} of
  844. {Same, Same} ->
  845. {Len, IdentityLen, identity};
  846. _ ->
  847. {Len, IdentityLen, gzip}
  848. end;
  849. gzip ->
  850. case {Att#att.att_len, Att#att.disk_len} of
  851. {AL, DL} when AL =:= undefined orelse DL =:= undefined ->
  852. % Compressed attachment uploaded through the standalone API.
  853. {Len, Len, gzip};
  854. {AL, DL} ->
  855. % This case is used for efficient push-replication, where a
  856. % compressed attachment is located in the body of multipart
  857. % content-type request.
  858. {AL, DL, gzip}
  859. end
  860. end,
  861. Att#att{
  862. data={Fd,StreamInfo},
  863. att_len=AttLen,
  864. disk_len=DiskLen,
  865. md5=Md5,
  866. encoding=NewEnc
  867. }.
  868. write_streamed_attachment(_Stream, _F, 0) ->
  869. ok;
  870. write_streamed_attachment(Stream, F, LenLeft) when LenLeft > 0 ->
  871. Bin = read_next_chunk(F, LenLeft),
  872. ok = couch_stream:write(Stream, Bin),
  873. write_streamed_attachment(Stream, F, LenLeft - size(Bin)).
  874. read_next_chunk(F, _) when is_function(F, 0) ->
  875. F();
  876. read_next_chunk(F, LenLeft) when is_function(F, 1) ->
  877. F(lists:min([LenLeft, 16#2000])).
  878. enum_docs_since_reduce_to_count(Reds) ->
  879. couch_btree:final_reduce(
  880. fun couch_db_updater:btree_by_seq_reduce/2, Reds).
  881. enum_docs_reduce_to_count(Reds) ->
  882. {Count, _, _} = couch_btree:final_reduce(
  883. fun couch_db_updater:btree_by_id_reduce/2, Reds),
  884. Count.
  885. changes_since(Db, StartSeq, Fun, Acc) ->
  886. changes_since(Db, StartSeq, Fun, [], Acc).
  887. changes_since(Db, StartSeq, Fun, Options, Acc) ->
  888. Wrapper = fun(FullDocInfo, _Offset, Acc2) ->
  889. case FullDocInfo of
  890. #full_doc_info{} ->
  891. DocInfo = couch_doc:to_doc_info(FullDocInfo);
  892. #doc_info{} ->
  893. DocInfo = FullDocInfo
  894. end,
  895. Fun(DocInfo, Acc2)
  896. end,
  897. {ok, _LastReduction, AccOut} = couch_btree:fold(Db#db.seq_tree, Wrapper,
  898. Acc, [{start_key, couch_util:to_integer(StartSeq) + 1} | Options]),
  899. {ok, AccOut}.
  900. count_changes_since(Db, SinceSeq) ->
  901. {ok, Changes} =
  902. couch_btree:fold_reduce(Db#db.seq_tree,
  903. fun(_SeqStart, PartialReds, 0) ->
  904. {ok, couch_btree:final_reduce(Db#db.seq_tree, PartialReds)}
  905. end,
  906. 0, [{start_key, SinceSeq + 1}]),
  907. Changes.
  908. enum_docs_since(Db, SinceSeq, InFun, Acc, Options) ->
  909. {ok, LastReduction, AccOut} = couch_btree:fold(Db#db.seq_tree, InFun, Acc, [{start_key, SinceSeq + 1} | Options]),
  910. {ok, enum_docs_since_reduce_to_count(LastReduction), AccOut}.
  911. enum_docs(Db, InFun, InAcc, Options) ->
  912. {ok, LastReduce, OutAcc} = couch_view:fold(
  913. #view{btree=Db#db.id_tree}, InFun, InAcc, Options),
  914. {ok, enum_docs_reduce_to_count(LastReduce), OutAcc}.
  915. %%% Internal function %%%
  916. open_doc_revs_int(Db, IdRevs, Options) ->
  917. Ids = [Id || {Id, _Revs} <- IdRevs],
  918. LookupResults = get_full_doc_infos(Db, Ids),
  919. lists:zipwith(
  920. fun({Id, Revs}, Lookup) ->
  921. case Lookup of
  922. {ok, #full_doc_info{rev_tree=RevTree}} ->
  923. {FoundRevs, MissingRevs} =
  924. case Revs of
  925. all ->
  926. {couch_key_tree:get_all_leafs(RevTree), []};
  927. _ ->
  928. case lists:member(latest, Options) of
  929. true ->
  930. couch_key_tree:get_key_leafs(RevTree, Revs);
  931. false ->
  932. couch_key_tree:get(RevTree, Revs)
  933. end
  934. end,
  935. FoundResults =
  936. lists:map(fun({Value, {Pos, [Rev|_]}=FoundRevPath}) ->
  937. case Value of
  938. ?REV_MISSING ->
  939. % we have the rev in our list but know nothing about it
  940. {{not_found, missing}, {Pos, Rev}};
  941. #leaf{deleted=IsDeleted, ptr=SummaryPtr} ->
  942. {ok, make_doc(Db, Id, IsDeleted, SummaryPtr, FoundRevPath)}
  943. end
  944. end, FoundRevs),
  945. Results = FoundResults ++ [{{not_found, missing}, MissingRev} || MissingRev <- MissingRevs],
  946. {ok, Results};
  947. not_found when Revs == all ->
  948. {ok, []};
  949. not_found ->
  950. {ok, [{{not_found, missing}, Rev} || Rev <- Revs]}
  951. end
  952. end,
  953. IdRevs, LookupResults).
  954. open_doc_int(Db, <<?LOCAL_DOC_PREFIX, _/binary>> = Id, _Options) ->
  955. case couch_btree:lookup(Db#db.local_tree, [Id]) of
  956. [{ok, {_, {Rev, BodyData}}}] ->
  957. {ok, #doc{id=Id, revs={0, [list_to_binary(integer_to_list(Rev))]}, body=BodyData}};
  958. [not_found] ->
  959. {not_found, missing}
  960. end;
  961. open_doc_int(Db, #doc_info{id=Id,revs=[RevInfo|_]}=DocInfo, Options) ->
  962. #rev_info{deleted=IsDeleted,rev={Pos,RevId},body_sp=Bp} = RevInfo,
  963. Doc = make_doc(Db, Id, IsDeleted, Bp, {Pos,[RevId]}),
  964. {ok, Doc#doc{meta=doc_meta_info(DocInfo, [], Options)}};
  965. open_doc_int(Db, #full_doc_info{id=Id,rev_tree=RevTree}=FullDocInfo, Options) ->
  966. #doc_info{revs=[#rev_info{deleted=IsDeleted,rev=Rev,body_sp=Bp}|_]} =
  967. DocInfo = couch_doc:to_doc_info(FullDocInfo),
  968. {[{_, RevPath}], []} = couch_key_tree:get(RevTree, [Rev]),
  969. Doc = make_doc(Db, Id, IsDeleted, Bp, RevPath),
  970. {ok, Doc#doc{meta=doc_meta_info(DocInfo, RevTree, Options)}};
  971. open_doc_int(Db, Id, Options) ->
  972. case get_full_doc_info(Db, Id) of
  973. {ok, FullDocInfo} ->
  974. open_doc_int(Db, FullDocInfo, Options);
  975. not_found ->
  976. {not_found, missing}
  977. end.
  978. doc_meta_info(#doc_info{high_seq=Seq,revs=[#rev_info{rev=Rev}|RestInfo]}, RevTree, Options) ->
  979. case lists:member(revs_info, Options) of
  980. false -> [];
  981. true ->
  982. {[{Pos, RevPath}],[]} =
  983. couch_key_tree:get_full_key_paths(RevTree, [Rev]),
  984. [{revs_info, Pos, lists:map(
  985. fun({Rev1, #leaf{deleted=true}}) ->
  986. {Rev1, deleted};
  987. ({Rev1, #leaf{deleted=false}}) ->
  988. {Rev1, available};
  989. ({Rev1, ?REV_MISSING}) ->
  990. {Rev1, missing}
  991. end, RevPath)}]
  992. end ++
  993. case lists:member(conflicts, Options) of
  994. false -> [];
  995. true ->
  996. case [Rev1 || #rev_info{rev=Rev1,deleted=false} <- RestInfo] of
  997. [] -> [];
  998. ConflictRevs -> [{conflicts, ConflictRevs}]
  999. end
  1000. end ++
  1001. case lists:member(deleted_conflicts, Options) of
  1002. false -> [];
  1003. true ->
  1004. case [Rev1 || #rev_info{rev=Rev1,deleted=true} <- RestInfo] of
  1005. [] -> [];
  1006. DelConflictRevs -> [{deleted_conflicts, DelConflictRevs}]
  1007. end
  1008. end ++
  1009. case lists:member(local_seq, Options) of
  1010. false -> [];
  1011. true -> [{local_seq, Seq}]
  1012. end.
  1013. read_doc(#db{fd=Fd}, OldStreamPointer) when is_tuple(OldStreamPointer) ->
  1014. % 09 UPGRADE CODE
  1015. couch_stream:old_read_term(Fd, OldStreamPointer);
  1016. read_doc(#db{fd=Fd}, Pos) ->
  1017. couch_file:pread_term(Fd, Pos).
  1018. doc_to_tree(#doc{revs={Start, RevIds}}=Doc) ->
  1019. [Tree] = doc_to_tree_simple(Doc, lists:reverse(RevIds)),
  1020. {Start - length(RevIds) + 1, Tree}.
  1021. doc_to_tree_simple(Doc, [RevId]) ->
  1022. [{RevId, Doc, []}];
  1023. doc_to_tree_simple(Doc, [RevId | Rest]) ->
  1024. [{RevId, ?REV_MISSING, doc_to_tree_simple(Doc, Rest)}].
  1025. make_doc(#db{fd=Fd}=Db, Id, Deleted, Bp, RevisionPath) ->
  1026. {BodyData, Atts} =
  1027. case Bp of
  1028. nil ->
  1029. {[], []};
  1030. _ ->
  1031. {ok, {BodyData0, Atts0}} = read_doc(Db, Bp),
  1032. {BodyData0,
  1033. lists:map(
  1034. fun({Name,Type,Sp,AttLen,DiskLen,RevPos,Md5,Enc}) ->
  1035. #att{name=Name,
  1036. type=Type,
  1037. att_len=AttLen,
  1038. disk_len=DiskLen,
  1039. md5=Md5,
  1040. revpos=RevPos,
  1041. data={Fd,Sp},
  1042. encoding=
  1043. case Enc of
  1044. true ->
  1045. % 0110 UPGRADE CODE
  1046. gzip;
  1047. false ->
  1048. % 0110 UPGRADE CODE
  1049. identity;
  1050. _ ->
  1051. Enc
  1052. end
  1053. };
  1054. ({Name,Type,Sp,AttLen,RevPos,Md5}) ->
  1055. #att{name=Name,
  1056. type=Type,
  1057. att_len=AttLen,
  1058. disk_len=AttLen,
  1059. md5=Md5,
  1060. revpos=RevPos,
  1061. data={Fd,Sp}};
  1062. ({Name,{Type,Sp,AttLen}}) ->
  1063. #att{name=Name,
  1064. type=Type,
  1065. att_len=AttLen,
  1066. disk_len=AttLen,
  1067. md5= <<>>,
  1068. revpos=0,
  1069. data={Fd,Sp}}
  1070. end, Atts0)}
  1071. end,
  1072. #doc{
  1073. id = Id,
  1074. revs = RevisionPath,
  1075. body = BodyData,
  1076. atts = Atts,
  1077. deleted = Deleted
  1078. }.
  1079. increment_stat(#db{is_sys_db = true}, _Stat) ->
  1080. ok;
  1081. increment_stat(#db{}, Stat) ->
  1082. couch_stats_collector:increment(Stat).