From 649b808af04720027c80390aecb6a5a01deb70b5 Mon Sep 17 00:00:00 2001 From: Edwin Fine Date: Sat, 13 Jan 2018 22:16:43 -0500 Subject: Allow override of `-args_file` and `-config` parameters (#1095) The existing `couchdb` start script hard-codes the arguments to `-args_file` and `-config`. Although it is possible to copy this script and modify it, or modify it in place, that is less than ideal and can lead to all kinds of difficulties. This PR adds the following environment variables: - `ARGS_FILE`: By default, set to the existing hard-coded value. - `SYSCONFIG_FILE`: By default, set to the existing hard-coded value. - `COUCHDB_ARGS_FILE`: If non-empty, overrides `ARGS_FILE`. - `COUCHDB_SYSCONFIG_FILE`: If non-empty, overrides `SYSCONFIG_FILE`. By changing the script to use these environment variables, it makes it easily possible to use different settings without tinkering with the pristine installed CouchDB environment. --- rel/overlay/bin/couchdb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rel/overlay/bin/couchdb b/rel/overlay/bin/couchdb index c82f581f4..a9e6e9bea 100755 --- a/rel/overlay/bin/couchdb +++ b/rel/overlay/bin/couchdb @@ -26,6 +26,10 @@ export BINDIR="$ROOTDIR/erts-$ERTS_VSN/bin" export EMU=beam export PROGNAME=`echo $0 | sed 's/.*\///'` +ARGS_FILE="${COUCHDB_ARGS_FILE:-$ROOTDIR/etc/vm.args}" +SYSCONFIG_FILE="${COUCHDB_SYSCONFIG_FILE:-$ROOTDIR/releases/$APP_VSN/sys.config}" + exec "$BINDIR/erlexec" -boot "$ROOTDIR/releases/$APP_VSN/couchdb" \ - -args_file "$ROOTDIR/etc/vm.args" \ - -config "$ROOTDIR/releases/$APP_VSN/sys.config" "$@" + -args_file "${ARGS_FILE}" \ + -config "${SYSCONFIG_FILE}" "$@" + -- cgit v1.2.1 From 567a16e51f4b1c41462eb10bc22f3f7ad7051a51 Mon Sep 17 00:00:00 2001 From: Eric Avdey Date: Mon, 15 Jan 2018 11:29:34 -0400 Subject: Fix couch_peruser_test Fix a random mashup of test object generators with normal assertions. --- src/couch_peruser/test/couch_peruser_test.erl | 67 ++++++++++++++++++--------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/couch_peruser/test/couch_peruser_test.erl b/src/couch_peruser/test/couch_peruser_test.erl index 04ef2ea90..1ce1964ed 100644 --- a/src/couch_peruser/test/couch_peruser_test.erl +++ b/src/couch_peruser/test/couch_peruser_test.erl @@ -196,10 +196,11 @@ should_not_delete_user_db(TestAuthDb) -> UserDbName = <<"userdb-666f6f">>, create_user(TestAuthDb, User), wait_for_db_create(<<"userdb-666f6f">>), - ?assert(lists:member(UserDbName, all_dbs())), + AfterCreate = lists:member(UserDbName, all_dbs()), delete_user(TestAuthDb, User), timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), - ?_assert(lists:member(UserDbName, all_dbs())). + AfterDelete = lists:member(UserDbName, all_dbs()), + [?_assert(AfterCreate), ?_assert(AfterDelete)]. should_delete_user_db(TestAuthDb) -> User = "bar", @@ -207,10 +208,11 @@ should_delete_user_db(TestAuthDb) -> set_config("couch_peruser", "delete_dbs", "true"), create_user(TestAuthDb, User), wait_for_db_create(UserDbName), - ?assert(lists:member(UserDbName, all_dbs())), + AfterCreate = lists:member(UserDbName, all_dbs()), delete_user(TestAuthDb, User), wait_for_db_delete(UserDbName), - ?_assert(not lists:member(UserDbName, all_dbs())). + AfterDelete = lists:member(UserDbName, all_dbs()), + [?_assert(AfterCreate), ?_assertNot(AfterDelete)]. should_reflect_config_changes(TestAuthDb) -> User = "baz", @@ -218,28 +220,37 @@ should_reflect_config_changes(TestAuthDb) -> set_config("couch_peruser", "delete_dbs", "true"), create_user(TestAuthDb, User), wait_for_db_create(UserDbName), - ?assert(lists:member(UserDbName, all_dbs())), + AfterCreate1 = lists:member(UserDbName, all_dbs()), delete_user(TestAuthDb, User), timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), wait_for_db_delete(UserDbName), - ?assert(not lists:member(UserDbName, all_dbs())), + AfterDelete1 = lists:member(UserDbName, all_dbs()), create_user(TestAuthDb, User), wait_for_db_create(UserDbName), - ?assert(lists:member(UserDbName, all_dbs())), + AfterCreate2 = lists:member(UserDbName, all_dbs()), set_config("couch_peruser", "delete_dbs", "false"), delete_user(TestAuthDb, User), timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), - ?assert(lists:member(UserDbName, all_dbs())), + AfterDelete2 = lists:member(UserDbName, all_dbs()), create_user(TestAuthDb, User), wait_for_db_create(UserDbName), set_config("couch_peruser", "delete_dbs", "true"), delete_user(TestAuthDb, User), wait_for_db_delete(UserDbName), - ?assert(not lists:member(UserDbName, all_dbs())), + AfterDelete3 = lists:member(UserDbName, all_dbs()), set_config("couch_peruser", "enable", "false"), create_user(TestAuthDb, User), timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), - ?_assert(not lists:member(UserDbName, all_dbs())). + AfterCreate3 = lists:member(UserDbName, all_dbs()), + [ + ?_assert(AfterCreate1), + ?_assertNot(AfterDelete1), + ?_assert(AfterCreate2), + ?_assert(AfterDelete2), + ?_assertNot(AfterDelete3), + ?_assertNot(AfterCreate3) + ]. + should_add_user_to_db_admins(TestAuthDb) -> User = "qux", @@ -313,18 +324,24 @@ should_remove_user_from_db_admins(TestAuthDb) -> {AdminProperties} = proplists:get_value(<<"admins">>, get_security(UserDbName)), AdminNames = proplists:get_value(<<"names">>, AdminProperties), - ?assert(lists:member(<<"foo">>, AdminNames)), - ?assert(lists:member(<<"bar">>, AdminNames)), - ?assert(lists:member(<<"qux">>, AdminNames)), + FooBefore = lists:member(<<"foo">>, AdminNames), + BarBefore = lists:member(<<"bar">>, AdminNames), + QuxBefore = lists:member(<<"qux">>, AdminNames), delete_user(TestAuthDb, User), wait_for_security_delete(<<"admins">>, User, UserDbName), {NewAdminProperties} = proplists:get_value(<<"admins">>, get_security(UserDbName)), NewAdminNames = proplists:get_value(<<"names">>, NewAdminProperties), + FooAfter = lists:member(<<"foo">>, NewAdminNames), + BarAfter = lists:member(<<"bar">>, NewAdminNames), + QuxAfter = lists:member(<<"qux">>, NewAdminNames), [ - ?_assert(lists:member(<<"foo">>, NewAdminNames)), - ?_assert(lists:member(<<"bar">>, NewAdminNames)), - ?_assert(not lists:member(<<"qux">>, NewAdminNames)) + ?_assert(FooBefore), + ?_assert(BarBefore), + ?_assert(QuxBefore), + ?_assert(FooAfter), + ?_assert(BarAfter), + ?_assertNot(QuxAfter) ]. should_remove_user_from_db_members(TestAuthDb) -> @@ -341,18 +358,24 @@ should_remove_user_from_db_members(TestAuthDb) -> {MemberProperties} = proplists:get_value(<<"members">>, get_security(UserDbName)), MemberNames = proplists:get_value(<<"names">>, MemberProperties), - ?assert(lists:member(<<"pow">>, MemberNames)), - ?assert(lists:member(<<"wow">>, MemberNames)), - ?assert(lists:member(<<"qux">>, MemberNames)), + PowBefore = lists:member(<<"pow">>, MemberNames), + WowBefore = lists:member(<<"wow">>, MemberNames), + QuxBefore = lists:member(<<"qux">>, MemberNames), delete_user(TestAuthDb, User), wait_for_security_delete(<<"members">>, User, UserDbName), {NewMemberProperties} = proplists:get_value(<<"members">>, get_security(UserDbName)), NewMemberNames = proplists:get_value(<<"names">>, NewMemberProperties), + PowAfter = lists:member(<<"pow">>, NewMemberNames), + WowAfter = lists:member(<<"wow">>, NewMemberNames), + QuxAfter = lists:member(<<"qux">>, NewMemberNames), [ - ?_assert(lists:member(<<"pow">>, NewMemberNames)), - ?_assert(lists:member(<<"wow">>, NewMemberNames)), - ?_assert(not lists:member(<<"qux">>, NewMemberNames)) + ?_assert(PowBefore), + ?_assert(WowBefore), + ?_assert(QuxBefore), + ?_assert(PowAfter), + ?_assert(WowAfter), + ?_assertNot(QuxAfter) ]. % infinite loop waiting for a db to be created, either this returns true -- cgit v1.2.1 From ba82c4e31d967c6f157392e264986bf48e146967 Mon Sep 17 00:00:00 2001 From: jiangphcn Date: Wed, 3 Jan 2018 17:58:54 +0800 Subject: Return null for update_seq and offset if update_seq is true issue 969 --- src/chttpd/test/chttpd_db_test.erl | 33 ++++++++++++++++++++++++++++++++- src/fabric/src/fabric_view_all_docs.erl | 10 ++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/chttpd/test/chttpd_db_test.erl b/src/chttpd/test/chttpd_db_test.erl index f3c779bd3..a83e33acc 100644 --- a/src/chttpd/test/chttpd_db_test.erl +++ b/src/chttpd/test/chttpd_db_test.erl @@ -20,6 +20,7 @@ -define(AUTH, {basic_auth, {?USER, ?PASS}}). -define(CONTENT_JSON, {"Content-Type", "application/json"}). -define(FIXTURE_TXT, ?ABS_PATH(?FILE)). +-define(i2l(I), integer_to_list(I)). setup() -> Hashed = couch_passwords:hash_admin_password(?PASS), @@ -62,7 +63,9 @@ all_test_() -> fun should_return_404_for_delete_att_on_notadoc/1, fun should_return_409_for_del_att_without_rev/1, fun should_return_200_for_del_att_with_rev/1, - fun should_return_409_for_put_att_nonexistent_rev/1 + fun should_return_409_for_put_att_nonexistent_rev/1, + fun should_return_update_seq_when_set_on_all_docs/1, + fun should_not_return_update_seq_when_unset_on_all_docs/1 ] } } @@ -187,6 +190,34 @@ should_return_409_for_put_att_nonexistent_rev(Url) -> end). +should_return_update_seq_when_set_on_all_docs(Url) -> + ?_test(begin + [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 3)], + {ok, RC, _, RespBody} = test_request:get(Url ++ "/_all_docs/" + ++ "?update_seq=true&keys=[\"testdoc1\"]",[?CONTENT_JSON, ?AUTH]), + ?assertEqual(200, RC), + {ResultJson} = ?JSON_DECODE(RespBody), + ?assertNotEqual(undefined, + couch_util:get_value(<<"update_seq">>, ResultJson)), + ?assertNotEqual(undefined, + couch_util:get_value(<<"offset">>, ResultJson)) + end). + + +should_not_return_update_seq_when_unset_on_all_docs(Url) -> + ?_test(begin + [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 3)], + {ok, RC, _, RespBody} = test_request:get(Url ++ "/_all_docs/" + ++ "?update_seq=false&keys=[\"testdoc1\"]",[?CONTENT_JSON, ?AUTH]), + ?assertEqual(200, RC), + {ResultJson} = ?JSON_DECODE(RespBody), + ?assertEqual(undefined, + couch_util:get_value(<<"update_seq">>, ResultJson)), + ?assertNotEqual(undefined, + couch_util:get_value(<<"offset">>, ResultJson)) + end). + + attachment_doc() -> {ok, Data} = file:read_file(?FIXTURE_TXT), {[ diff --git a/src/fabric/src/fabric_view_all_docs.erl b/src/fabric/src/fabric_view_all_docs.erl index de21dde08..ac16dac52 100644 --- a/src/fabric/src/fabric_view_all_docs.erl +++ b/src/fabric/src/fabric_view_all_docs.erl @@ -59,7 +59,8 @@ go(DbName, Options, QueryArgs, Callback, Acc0) -> conflicts = Conflicts, skip = Skip, keys = Keys0, - extra = Extra + extra = Extra, + update_seq = UpdateSeq } = QueryArgs, DocOptions1 = case Conflicts of true -> [conflicts|DocOptions0]; @@ -97,7 +98,12 @@ go(DbName, Options, QueryArgs, Callback, Acc0) -> end, case Resp of {ok, TotalRows} -> - {ok, Acc1} = Callback({meta, [{total, TotalRows}]}, Acc0), + Meta = case UpdateSeq of + false -> [{total, TotalRows}, {offset, null}]; + true -> + [{total, TotalRows}, {offset, null}, {update_seq, null}] + end, + {ok, Acc1} = Callback({meta, Meta}, Acc0), {ok, Acc2} = doc_receive_loop( Keys3, queue:new(), SpawnFun, MaxJobs, Callback, Acc1 ), -- cgit v1.2.1 From b43c401f6e15676dd1514628132898c67b1763c0 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Wed, 17 Jan 2018 13:19:57 -0500 Subject: Create all needed directories to build docs (#1115) --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 248dddc58..e95d04419 100644 --- a/Makefile +++ b/Makefile @@ -276,6 +276,7 @@ ifeq ($(IN_RELEASE), true) @cp -R share/docs/html/* rel/couchdb/share/www/docs/ @cp share/docs/man/apachecouchdb.1 rel/couchdb/share/docs/couchdb.1 else + @mkdir -p rel/couchdb/share/www/docs/ @mkdir -p rel/couchdb/share/docs/ @cp -R src/docs/build/html/ rel/couchdb/share/www/docs @cp src/docs/build/man/apachecouchdb.1 rel/couchdb/share/docs/couchdb.1 -- cgit v1.2.1 From 0fd950905a76f3eaab685fc361bd3f38471761d5 Mon Sep 17 00:00:00 2001 From: jiangphcn Date: Thu, 30 Nov 2017 17:52:08 +0800 Subject: Add support for queries in /{db}/_all_docs POST Fixes #820 --- src/chttpd/src/chttpd_db.erl | 42 ++++++++++++--- src/chttpd/test/chttpd_db_test.erl | 87 +++++++++++++++++++++++++++++- src/couch_mrview/src/couch_mrview_util.erl | 4 +- test/javascript/tests/basics.js | 2 +- test/javascript/tests/view_errors.js | 2 +- 5 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl index dbbb454cb..e621d657a 100644 --- a/src/chttpd/src/chttpd_db.erl +++ b/src/chttpd/src/chttpd_db.erl @@ -520,14 +520,18 @@ db_req(#httpd{method='GET',path_parts=[_,OP]}=Req, Db) when ?IS_ALL_DOCS(OP) -> db_req(#httpd{method='POST',path_parts=[_,OP]}=Req, Db) when ?IS_ALL_DOCS(OP) -> chttpd:validate_ctype(Req, "application/json"), - {Fields} = chttpd:json_body_obj(Req), - case couch_util:get_value(<<"keys">>, Fields, nil) of - Keys when is_list(Keys) -> - all_docs_view(Req, Db, Keys, OP); - nil -> - all_docs_view(Req, Db, undefined, OP); - _ -> - throw({bad_request, "`keys` body member must be an array."}) + Props = chttpd:json_body_obj(Req), + Keys = couch_mrview_util:get_view_keys(Props), + Queries = couch_mrview_util:get_view_queries(Props), + case {Queries, Keys} of + {Queries, undefined} when is_list(Queries) -> + multi_all_docs_view(Req, Db, OP, Queries); + {undefined, Keys} when is_list(Keys) -> + all_docs_view(Req, Db, Keys, OP); + {undefined, undefined} -> + all_docs_view(Req, Db, undefined, OP); + {_, _} -> + throw({bad_request, "`keys` and `queries` are mutually exclusive"}) end; db_req(#httpd{path_parts=[_,OP]}=Req, _Db) when ?IS_ALL_DOCS(OP) -> @@ -636,6 +640,28 @@ db_req(#httpd{path_parts=[_, DocId]}=Req, Db) -> db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) -> db_attachment_req(Req, Db, DocId, FileNameParts). +multi_all_docs_view(Req, Db, OP, Queries) -> + Args0 = couch_mrview_http:parse_params(Req, undefined), + Args1 = Args0#mrargs{view_type=map}, + ArgQueries = lists:map(fun({Query}) -> + QueryArg1 = couch_mrview_http:parse_params(Query, undefined, + Args1, [decoded]), + QueryArgs2 = couch_mrview_util:validate_args(QueryArg1), + set_namespace(OP, QueryArgs2) + end, Queries), + Options = [{user_ctx, Req#httpd.user_ctx}], + VAcc0 = #vacc{db=Db, req=Req, prepend="\r\n"}, + FirstChunk = "{\"results\":[", + {ok, Resp0} = chttpd:start_delayed_json_response(VAcc0#vacc.req, 200, [], FirstChunk), + VAcc1 = VAcc0#vacc{resp=Resp0}, + VAcc2 = lists:foldl(fun(Args, Acc0) -> + {ok, Acc1} = fabric:all_docs(Db, Options, + fun couch_mrview_http:view_cb/2, Acc0, Args), + Acc1 + end, VAcc1, ArgQueries), + {ok, Resp1} = chttpd:send_delayed_chunk(VAcc2#vacc.resp, "\r\n]}"), + chttpd:end_delayed_json_response(Resp1). + all_docs_view(Req, Db, Keys, OP) -> Args0 = couch_mrview_http:parse_params(Req, Keys), Args1 = Args0#mrargs{view_type=map}, diff --git a/src/chttpd/test/chttpd_db_test.erl b/src/chttpd/test/chttpd_db_test.erl index a83e33acc..f6732939c 100644 --- a/src/chttpd/test/chttpd_db_test.erl +++ b/src/chttpd/test/chttpd_db_test.erl @@ -65,7 +65,12 @@ all_test_() -> fun should_return_200_for_del_att_with_rev/1, fun should_return_409_for_put_att_nonexistent_rev/1, fun should_return_update_seq_when_set_on_all_docs/1, - fun should_not_return_update_seq_when_unset_on_all_docs/1 + fun should_not_return_update_seq_when_unset_on_all_docs/1, + fun should_succeed_on_all_docs_with_queries_keys/1, + fun should_succeed_on_all_docs_with_queries_limit_skip/1, + fun should_succeed_on_all_docs_with_multiple_queries/1, + fun should_succeed_on_design_docs_with_multiple_queries/1, + fun should_fail_on_multiple_queries_with_keys_and_queries/1 ] } } @@ -218,6 +223,86 @@ should_not_return_update_seq_when_unset_on_all_docs(Url) -> end). +should_succeed_on_all_docs_with_queries_keys(Url) -> + ?_test(begin + [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)], + QueryDoc = "{\"queries\": [{\"keys\": [ \"testdoc3\", \"testdoc8\"]}]}", + {ok, RC, _, RespBody} = test_request:post(Url ++ "/_all_docs/", + [?CONTENT_JSON, ?AUTH], QueryDoc), + ?assertEqual(200, RC), + {ResultJson} = ?JSON_DECODE(RespBody), + ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson), + {InnerJson} = lists:nth(1, ResultJsonBody), + ?assertEqual(2, length(couch_util:get_value(<<"rows">>, InnerJson))) + end). + + +should_succeed_on_all_docs_with_queries_limit_skip(Url) -> + ?_test(begin + [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)], + QueryDoc = "{\"queries\": [{\"limit\": 5, \"skip\": 2}]}", + {ok, RC, _, RespBody} = test_request:post(Url ++ "/_all_docs/", + [?CONTENT_JSON, ?AUTH], QueryDoc), + ?assertEqual(200, RC), + {ResultJson} = ?JSON_DECODE(RespBody), + ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson), + {InnerJson} = lists:nth(1, ResultJsonBody), + ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson)), + ?assertEqual(5, length(couch_util:get_value(<<"rows">>, InnerJson))) + end). + + +should_succeed_on_all_docs_with_multiple_queries(Url) -> + ?_test(begin + [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)], + QueryDoc = "{\"queries\": [{\"keys\": [ \"testdoc3\", \"testdoc8\"]}, + {\"limit\": 5, \"skip\": 2}]}", + {ok, RC, _, RespBody} = test_request:post(Url ++ "/_all_docs/", + [?CONTENT_JSON, ?AUTH], QueryDoc), + ?assertEqual(200, RC), + {ResultJson} = ?JSON_DECODE(RespBody), + ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson), + {InnerJson1} = lists:nth(1, ResultJsonBody), + ?assertEqual(2, length(couch_util:get_value(<<"rows">>, InnerJson1))), + {InnerJson2} = lists:nth(2, ResultJsonBody), + ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson2)), + ?assertEqual(5, length(couch_util:get_value(<<"rows">>, InnerJson2))) + end). + + +should_succeed_on_design_docs_with_multiple_queries(Url) -> + ?_test(begin + [create_doc(Url, "_design/ddoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)], + QueryDoc = "{\"queries\": [{\"keys\": [ \"_design/ddoc3\", + \"_design/ddoc8\"]}, {\"limit\": 5, \"skip\": 2}]}", + {ok, RC, _, RespBody} = test_request:post(Url ++ "/_design_docs/", + [?CONTENT_JSON, ?AUTH], QueryDoc), + ?assertEqual(200, RC), + {ResultJson} = ?JSON_DECODE(RespBody), + ResultJsonBody = couch_util:get_value(<<"results">>, ResultJson), + {InnerJson1} = lists:nth(1, ResultJsonBody), + ?assertEqual(2, length(couch_util:get_value(<<"rows">>, InnerJson1))), + {InnerJson2} = lists:nth(2, ResultJsonBody), + ?assertEqual(2, couch_util:get_value(<<"offset">>, InnerJson2)), + ?assertEqual(5, length(couch_util:get_value(<<"rows">>, InnerJson2))) + end). + + +should_fail_on_multiple_queries_with_keys_and_queries(Url) -> + ?_test(begin + [create_doc(Url, "testdoc" ++ ?i2l(I)) || I <- lists:seq(1, 10)], + QueryDoc = "{\"queries\": [{\"keys\": [ \"testdoc3\", \"testdoc8\"]}], + \"keys\": [ \"testdoc4\", \"testdoc9\"]}", + {ok, RC, _, RespBody} = test_request:post(Url ++ "/_all_docs/", + [?CONTENT_JSON, ?AUTH], QueryDoc), + ?assertEqual(400, RC), + ?assertMatch({[ + {<<"error">>,<<"bad_request">>}, + {<<"reason">>,<<"`keys` and `queries` are mutually exclusive">>}]}, + ?JSON_DECODE(RespBody)) + end). + + attachment_doc() -> {ok, Data} = file:read_file(?FIXTURE_TXT), {[ diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl index d26df94f2..bc6686b8a 100644 --- a/src/couch_mrview/src/couch_mrview_util.erl +++ b/src/couch_mrview/src/couch_mrview_util.erl @@ -1161,7 +1161,7 @@ get_view_keys({Props}) -> Keys when is_list(Keys) -> Keys; _ -> - throw({bad_request, "`keys` member must be a array."}) + throw({bad_request, "`keys` member must be an array."}) end. @@ -1172,7 +1172,7 @@ get_view_queries({Props}) -> Queries when is_list(Queries) -> Queries; _ -> - throw({bad_request, "`queries` member must be a array."}) + throw({bad_request, "`queries` member must be an array."}) end. diff --git a/test/javascript/tests/basics.js b/test/javascript/tests/basics.js index a36b3035d..79599516d 100644 --- a/test/javascript/tests/basics.js +++ b/test/javascript/tests/basics.js @@ -268,7 +268,7 @@ couchTests.basics = function(debug) { T(xhr.status == 400); result = JSON.parse(xhr.responseText); T(result.error == "bad_request"); - T(result.reason == "`keys` body member must be an array."); + T(result.reason == "`keys` member must be an array."); // oops, the doc id got lost in code nirwana xhr = CouchDB.request("DELETE", "/" + db_name + "/?rev=foobarbaz"); diff --git a/test/javascript/tests/view_errors.js b/test/javascript/tests/view_errors.js index dd60292a3..f135b749a 100644 --- a/test/javascript/tests/view_errors.js +++ b/test/javascript/tests/view_errors.js @@ -169,7 +169,7 @@ couchTests.view_errors = function(debug) { T(xhr.status == 400); result = JSON.parse(xhr.responseText); T(result.error == "bad_request"); - T(result.reason == "`keys` member must be a array."); + T(result.reason == "`keys` member must be an array."); // if the reduce grows to fast, throw an overflow error var path = "/" + db_name + "/_design/testbig/_view/reduce_too_big"; -- cgit v1.2.1 From 91f5985e224db1936c41d6bf31eddba27f90bf98 Mon Sep 17 00:00:00 2001 From: Eric Avdey Date: Mon, 22 Jan 2018 16:34:26 -0400 Subject: Set eunit timeout on a whole test object --- src/couch/test/couch_db_tests.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/couch/test/couch_db_tests.erl b/src/couch/test/couch_db_tests.erl index 62e059070..d64f7c640 100644 --- a/src/couch/test/couch_db_tests.erl +++ b/src/couch/test/couch_db_tests.erl @@ -128,15 +128,13 @@ should_delete_multiple_dbs(DbNames) -> should_create_delete_database_continuously(Times, DbName) -> {lists:flatten(io_lib:format("~b times", [Times])), - ?_test(begin + {timeout, ?TIMEOUT, ?_test(begin ?assert(create_db(DbName)), lists:foreach(fun(_) -> - {timeout, ?TIMEOUT, [ - ?assert(delete_db(DbName)), - ?assert(create_db(DbName)) - ]} + ?assert(delete_db(DbName)), + ?assert(create_db(DbName)) end, lists:seq(1, Times)) - end)}. + end)}}. should_create_db_if_missing(DbName) -> ?_test(begin -- cgit v1.2.1 From d16f2db901c9b3b24c7189acfec35ec42895bd25 Mon Sep 17 00:00:00 2001 From: jiangphcn Date: Fri, 15 Dec 2017 15:07:04 +0800 Subject: Make peruser database prefix configurable Fixes #876 --- rel/overlay/etc/default.ini | 4 ++ src/couch_peruser/src/couch_peruser.erl | 52 ++++++++++++-------- src/couch_peruser/test/couch_peruser_test.erl | 68 +++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index c473495fe..7e429f624 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -91,6 +91,10 @@ delete_dbs = false ; Set a default q value for peruser-created databases that is different from ; cluster / q ;q = 1 +; prefix for user databases. If you change this after user dbs have been +; created, the existing databases won’t get deleted if the associated user +; gets deleted because of the then prefix mismatch. +database_prefix = userdb- [httpd] port = {{backend_port}} diff --git a/src/couch_peruser/src/couch_peruser.erl b/src/couch_peruser/src/couch_peruser.erl index bbf40126c..886fb4f6e 100644 --- a/src/couch_peruser/src/couch_peruser.erl +++ b/src/couch_peruser/src/couch_peruser.erl @@ -35,7 +35,8 @@ delete_dbs :: boolean(), changes_pid :: pid(), changes_ref :: reference(), - q_for_peruser_db :: integer() + q_for_peruser_db :: integer(), + peruser_dbname_prefix :: binary() }). -record(state, { @@ -45,10 +46,11 @@ states :: list(), mem3_cluster_pid :: pid(), cluster_stable :: boolean(), - q_for_peruser_db :: integer() + q_for_peruser_db :: integer(), + peruser_dbname_prefix :: binary() }). --define(USERDB_PREFIX, "userdb-"). +-define(DEFAULT_USERDB_PREFIX, "userdb-"). -define(RELISTEN_DELAY, 5000). -define(DEFAULT_QUIET_PERIOD, 60). % seconds -define(DEFAULT_START_PERIOD, 5). % seconds @@ -73,6 +75,14 @@ init_state() -> "couch_httpd_auth", "authentication_db", "_users")), DeleteDbs = config:get_boolean("couch_peruser", "delete_dbs", false), Q = config:get_integer("couch_peruser", "q", 1), + Prefix = config:get("couch_peruser", "database_prefix", ?DEFAULT_USERDB_PREFIX), + case couch_db:validate_dbname(Prefix) of + ok -> ok; + Error -> + couch_log:error("couch_peruser can't proceed as illegal database prefix ~p. + Error: ~p", [Prefix, Error]), + throw(Error) + end, % set up cluster-stable listener @@ -90,7 +100,8 @@ init_state() -> delete_dbs = DeleteDbs, mem3_cluster_pid = Mem3Cluster, cluster_stable = false, - q_for_peruser_db = Q + q_for_peruser_db = Q, + peruser_dbname_prefix = ?l2b(Prefix) } end. @@ -100,7 +111,8 @@ start_listening(#state{states=ChangesStates}=State) when length(ChangesStates) > 0 -> % couch_log:debug("peruser: start_listening() already run on node ~p in pid ~p", [node(), self()]), State; -start_listening(#state{db_name=DbName, delete_dbs=DeleteDbs, q_for_peruser_db = Q} = State) -> +start_listening(#state{db_name=DbName, delete_dbs=DeleteDbs, + q_for_peruser_db = Q, peruser_dbname_prefix = Prefix} = State) -> % couch_log:debug("peruser: start_listening() on node ~p", [node()]), try States = lists:map(fun (A) -> @@ -108,7 +120,8 @@ start_listening(#state{db_name=DbName, delete_dbs=DeleteDbs, q_for_peruser_db = parent = State#state.parent, db_name = A#shard.name, delete_dbs = DeleteDbs, - q_for_peruser_db = Q + q_for_peruser_db = Q, + peruser_dbname_prefix = Prefix }, {Pid, Ref} = spawn_opt( ?MODULE, init_changes_handler, [S], [link, monitor]), @@ -144,7 +157,8 @@ init_changes_handler(#changes_state{db_name=DbName} = ChangesState) -> changes_handler( {change, {Doc}, _Prepend}, _ResType, - ChangesState=#changes_state{db_name=DbName, q_for_peruser_db = Q}) -> + ChangesState=#changes_state{db_name=DbName, q_for_peruser_db = Q, + peruser_dbname_prefix = Prefix}) -> % couch_log:debug("peruser: changes_handler() on DbName/Doc ~p/~p", [DbName, Doc]), case couch_util:get_value(<<"id">>, Doc) of @@ -153,16 +167,16 @@ changes_handler( true -> case couch_util:get_value(<<"deleted">>, Doc, false) of false -> - UserDb = ensure_user_db(User, Q), + UserDb = ensure_user_db(Prefix, User, Q), ok = ensure_security(User, UserDb, fun add_user/3), ChangesState; true -> case ChangesState#changes_state.delete_dbs of true -> - _UserDb = delete_user_db(User), + _UserDb = delete_user_db(Prefix, User), ChangesState; false -> - UserDb = user_db_name(User), + UserDb = user_db_name(Prefix, User), ok = ensure_security(User, UserDb, fun remove_user/3), ChangesState end @@ -207,9 +221,9 @@ should_handle_doc_int(ShardName, DocId) -> false end. --spec delete_user_db(User :: binary()) -> binary(). -delete_user_db(User) -> - UserDb = user_db_name(User), +-spec delete_user_db(Prefix:: binary(), User :: binary()) -> binary(). +delete_user_db(Prefix, User) -> + UserDb = user_db_name(Prefix, User), try case fabric:delete_db(UserDb, [?ADMIN_CTX]) of ok -> ok; @@ -220,9 +234,9 @@ delete_user_db(User) -> end, UserDb. --spec ensure_user_db(User :: binary(), Q :: integer()) -> binary(). -ensure_user_db(User, Q) -> - UserDb = user_db_name(User), +-spec ensure_user_db(Prefix:: binary(), User :: binary(), Q :: integer()) -> binary(). +ensure_user_db(Prefix, User, Q) -> + UserDb = user_db_name(Prefix, User), try {ok, _DbInfo} = fabric:get_db_info(UserDb) catch error:database_does_not_exist -> @@ -300,11 +314,11 @@ ensure_security(User, UserDb, TransformFun) -> end end. --spec user_db_name(User :: binary()) -> binary(). -user_db_name(User) -> +-spec user_db_name(Prefix :: binary(), User :: binary()) -> binary(). +user_db_name(Prefix, User) -> HexUser = list_to_binary( [string:to_lower(integer_to_list(X, 16)) || <> <= User]), - <>. + <>. -spec exit_changes(State :: #state{}) -> ok. exit_changes(State) -> diff --git a/src/couch_peruser/test/couch_peruser_test.erl b/src/couch_peruser/test/couch_peruser_test.erl index 1ce1964ed..f6ef88f0b 100644 --- a/src/couch_peruser/test/couch_peruser_test.erl +++ b/src/couch_peruser/test/couch_peruser_test.erl @@ -156,6 +156,20 @@ should_create_user_db_with_default(TestAuthDb) -> ?_assertEqual(1, couch_util:get_value(q, ClusterInfo)) ]. +should_create_user_db_with_custom_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "newuserdb-"), + create_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"newuserdb-666f6f6f">>), + delete_config("couch_peruser", "database_prefix", "newuserdb-"), + ?_assert(lists:member(<<"newuserdb-666f6f6f">>, all_dbs())). + +should_create_user_db_with_custom_special_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + create_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"userdb_$()+--/666f6f6f">>), + delete_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + ?_assert(lists:member(<<"userdb_$()+--/666f6f6f">>, all_dbs())). + should_create_anon_user_db_with_default(TestAuthDb) -> create_anon_user(TestAuthDb, "fooo"), wait_for_db_create(<<"userdb-666f6f6f">>), @@ -166,6 +180,20 @@ should_create_anon_user_db_with_default(TestAuthDb) -> ?_assertEqual(1, couch_util:get_value(q, ClusterInfo)) ]. +should_create_anon_user_db_with_custom_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "newuserdb-"), + create_anon_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"newuserdb-666f6f6f">>), + delete_config("couch_peruser", "database_prefix", "newuserdb-"), + ?_assert(lists:member(<<"newuserdb-666f6f6f">>, all_dbs())). + +should_create_anon_user_db_with_custom_special_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + create_anon_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"userdb_$()+--/666f6f6f">>), + delete_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + ?_assert(lists:member(<<"userdb_$()+--/666f6f6f">>, all_dbs())). + should_create_user_db_with_q4(TestAuthDb) -> set_config("couch_peruser", "q", "4"), create_user(TestAuthDb, "foo"), @@ -214,6 +242,40 @@ should_delete_user_db(TestAuthDb) -> AfterDelete = lists:member(UserDbName, all_dbs()), [?_assert(AfterCreate), ?_assertNot(AfterDelete)]. +should_delete_user_db_with_custom_prefix(TestAuthDb) -> + User = "bar", + UserDbName = <<"newuserdb-626172">>, + set_config("couch_peruser", "delete_dbs", "true"), + set_config("couch_peruser", "database_prefix", "newuserdb-"), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + wait_for_db_delete(UserDbName), + delete_config("couch_peruser", "database_prefix", "newuserdb-"), + AfterDelete = lists:member(UserDbName, all_dbs()), + [ + ?_assert(AfterCreate), + ?_assertNot(AfterDelete) + ]. + +should_delete_user_db_with_custom_special_prefix(TestAuthDb) -> + User = "bar", + UserDbName = <<"userdb_$()+--/626172">>, + set_config("couch_peruser", "delete_dbs", "true"), + set_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + wait_for_db_delete(UserDbName), + delete_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + AfterDelete = lists:member(UserDbName, all_dbs()), + [ + ?_assert(AfterCreate), + ?_assertNot(AfterDelete) + ]. + should_reflect_config_changes(TestAuthDb) -> User = "baz", UserDbName = <<"userdb-62617a">>, @@ -445,11 +507,17 @@ couch_peruser_test_() -> fun setup/0, fun teardown/1, [ fun should_create_anon_user_db_with_default/1, + fun should_create_anon_user_db_with_custom_prefix/1, + fun should_create_anon_user_db_with_custom_special_prefix/1, fun should_create_user_db_with_default/1, + fun should_create_user_db_with_custom_prefix/1, + fun should_create_user_db_with_custom_special_prefix/1, fun should_create_user_db_with_q4/1, fun should_create_anon_user_db_with_q4/1, fun should_not_delete_user_db/1, fun should_delete_user_db/1, + fun should_delete_user_db_with_custom_prefix/1, + fun should_delete_user_db_with_custom_special_prefix/1, fun should_reflect_config_changes/1, fun should_add_user_to_db_admins/1, fun should_add_user_to_db_members/1, -- cgit v1.2.1