summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Kocoloski <adam@kocolosk.net>2018-01-24 11:02:09 -0600
committerGitHub <noreply@github.com>2018-01-24 11:02:09 -0600
commit79cc7ebe720017ec6dd038cb5d7bd3f9fbe66f49 (patch)
tree904a3587d2f29702683a2b270198780ddd8e9f3f
parent52cf451215b1fa6ba0acb4aa8bdd095d65903569 (diff)
parentd16f2db901c9b3b24c7189acfec35ec42895bd25 (diff)
downloadcouchdb-remove-outdated-docker-targets.tar.gz
Merge branch 'master' into remove-outdated-docker-targetsremove-outdated-docker-targets
-rw-r--r--Makefile1
-rwxr-xr-xrel/overlay/bin/couchdb8
-rw-r--r--rel/overlay/etc/default.ini4
-rw-r--r--src/chttpd/src/chttpd_db.erl42
-rw-r--r--src/chttpd/test/chttpd_db_test.erl118
-rw-r--r--src/couch/test/couch_db_tests.erl10
-rw-r--r--src/couch_mrview/src/couch_mrview_util.erl4
-rw-r--r--src/couch_peruser/src/couch_peruser.erl52
-rw-r--r--src/couch_peruser/test/couch_peruser_test.erl135
-rw-r--r--src/fabric/src/fabric_view_all_docs.erl10
-rw-r--r--test/javascript/tests/basics.js2
-rw-r--r--test/javascript/tests/view_errors.js2
12 files changed, 324 insertions, 64 deletions
diff --git a/Makefile b/Makefile
index 1155484e1..c8c0b093f 100644
--- a/Makefile
+++ b/Makefile
@@ -258,6 +258,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
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}" "$@"
+
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/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 f3c779bd3..f6732939c 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,14 @@ 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,
+ 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
]
}
}
@@ -187,6 +195,114 @@ 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).
+
+
+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/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
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/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)) || <<X>> <= User]),
- <<?USERDB_PREFIX,HexUser/binary>>.
+ <<Prefix/binary,HexUser/binary>>.
-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 04ef2ea90..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"),
@@ -196,10 +224,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 +236,45 @@ 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),
+ 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),
- ?_assert(not lists:member(UserDbName, all_dbs())).
+ delete_config("couch_peruser", "database_prefix", "userdb_$()+--/"),
+ AfterDelete = lists:member(UserDbName, all_dbs()),
+ [
+ ?_assert(AfterCreate),
+ ?_assertNot(AfterDelete)
+ ].
should_reflect_config_changes(TestAuthDb) ->
User = "baz",
@@ -218,28 +282,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 +386,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 +420,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
@@ -422,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,
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
),
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";