<commit_before>% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
%   http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

-module(chttpd_db).
-include("chttpd.hrl").

-export([handle_request/1, handle_compact_req/2, handle_design_req/2,
    db_req/2, couch_doc_open/4,handle_changes_req/2,
    update_doc_result_to_json/1, update_doc_result_to_json/2,
    handle_design_info_req/3, handle_view_cleanup_req/2]).

-import(chttpd,
    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
    start_json_response/2,send_chunk/2,end_json_response/1,
    start_chunked_response/3, absolute_uri/2, send/2,
    start_response_length/4]).

-record(doc_query_args, {
    options = [],
    rev = nil,
    open_revs = [],
    show = nil
}).

% Database request handlers
handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
        db_url_handlers=DbUrlHandlers}=Req)->
    case {Method, RestParts} of
    {'PUT', []} ->
        create_db_req(Req, DbName);
    {'DELETE', []} ->
        delete_db_req(Req, DbName);
    {_, []} ->
        do_db_req(Req, fun db_req/2);
    {_, [SecondPart|_]} ->
        Handler = couch_util:get_value(SecondPart, DbUrlHandlers, fun db_req/2),
        do_db_req(Req, Handler)
    end.

handle_changes_req(#httpd{method='GET'}=Req, Db) ->
    #changes_args{filter=Filter} = Args0 = parse_changes_query(Req),
    ChangesArgs = Args0#changes_args{filter=make_filter_fun(Filter, Req, Db)},
    case ChangesArgs#changes_args.feed of
    "normal" ->
        T0 = now(),
        {ok, Info} = fabric:get_db_info(Db),
        Etag = chttpd:make_etag(Info),
        DeltaT = timer:now_diff(now(), T0) / 1000,
        couch_stats_collector:record({couchdb, dbinfo}, DeltaT),
        chttpd:etag_respond(Req, Etag, fun() ->
            {ok, Resp} = chttpd:start_json_response(Req, 200, [{"Etag",Etag}]),
            fabric:changes(Db, ChangesArgs, fun changes_callback/2,
                {"normal", Resp})
        end);
    Feed ->
        % "longpoll" or "continuous"
        {ok, Resp} = chttpd:start_json_response(Req, 200),
        fabric:changes(Db, ChangesArgs, fun changes_callback/2, {Feed, Resp})
    end;
handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
    send_method_not_allowed(Req, "GET,HEAD").

% callbacks for continuous feed (newline-delimited JSON Objects)
changes_callback(start, {"continuous", _} = Acc) ->
    {ok, Acc};
changes_callback({change, Change}, {"continuous", Resp} = Acc) ->
    send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]),
    {ok, Acc};
changes_callback({stop, EndSeq0}, {"continuous", Resp}) ->
    EndSeq = case is_old_couch(Resp) of true -> 0; false -> EndSeq0 end,
    send_chunk(Resp, [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]),
    end_json_response(Resp);

% callbacks for longpoll and normal (single JSON Object)
changes_callback(start, {_, Resp}) ->
    send_chunk(Resp, "{\"results\":[\n"),
    {ok, {"", Resp}};
changes_callback({change, Change}, {Prepend, Resp}) ->
    send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]),
    {ok, {",\r\n", Resp}};
changes_callback({stop, EndSeq}, {_, Resp}) ->
    case is_old_couch(Resp) of
    true ->
        send_chunk(Resp, "\n],\n\"last_seq\":0}\n");
    false ->
        send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":\"~s\"}\n",[EndSeq]))
    end,
    end_json_response(Resp);

changes_callback(timeout, {Prepend, Resp}) ->
    send_chunk(Resp, "\n"),
    {ok, {Prepend, Resp}};
changes_callback({error, Reason}, Resp) ->
    chttpd:send_chunked_error(Resp, {error, Reason}).

is_old_couch(Resp) ->
    MochiReq = Resp:get(request),
    case MochiReq:get_header_value("user-agent") of
    undefined ->
        false;
    UserAgent ->
        string:str(UserAgent, "CouchDB/0") > 0
    end.

handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
    ok = ?COUCH:compact_view_group(DbName, Id),
    send_json(Req, 202, {[{ok, true}]});

handle_compact_req(Req, _) ->
    Msg = <<"Compaction is handled automatically by Cloudant">>,
    chttpd:send_error(Req, 403, Msg).

handle_view_cleanup_req(Req, _) ->
    Msg = <<"Old view indices are purged automatically by Cloudant">>,
    chttpd:send_error(Req, 403, Msg).

handle_design_req(#httpd{
        path_parts=[_DbName, _Design, Name, <<"_",_/binary>> = Action | _Rest],
        design_url_handlers = DesignUrlHandlers
    }=Req, Db) ->
    case fabric:open_doc(Db, <<"_design/", Name/binary>>, []) of
    {ok, DDoc} ->
        % TODO we'll trigger a badarity here if ddoc attachment starts with "_",
        % or if user tries an unknown Action
        Handler = couch_util:get_value(Action, DesignUrlHandlers, fun db_req/2),
        Handler(Req, Db, DDoc);
    Error ->
        throw(Error)
    end;

handle_design_req(Req, Db) ->
    db_req(Req, Db).

handle_design_info_req(#httpd{method='GET'}=Req, Db, #doc{id=Id} = DDoc) ->
    {ok, GroupInfoList} = fabric:get_view_group_info(Db, DDoc),
    send_json(Req, 200, {[
        {name,  Id},
        {view_index, {GroupInfoList}}
    ]});

handle_design_info_req(Req, _Db, _DDoc) ->
    send_method_not_allowed(Req, "GET").

create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
    N = chttpd:qs_value(Req, "n"),
    Q = chttpd:qs_value(Req, "q"),
    case fabric:create_db(DbName, [{user_ctx, UserCtx},{n,N},{q,Q}]) of
    ok ->
        DocUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)),
        send_json(Req, 201, [{"Location", DocUrl}], {[{ok, true}]});
    {error, file_exists} ->
        chttpd:send_error(Req, file_exists);
    Error ->
        throw(Error)
    end.
<commit_msg>supply proper default values to fabric on db create<commit_after>% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
%   http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

-module(chttpd_db).
-include("chttpd.hrl").

-export([handle_request/1, handle_compact_req/2, handle_design_req/2,
    db_req/2, couch_doc_open/4,handle_changes_req/2,
    update_doc_result_to_json/1, update_doc_result_to_json/2,
    handle_design_info_req/3, handle_view_cleanup_req/2]).

-import(chttpd,
    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
    start_json_response/2,send_chunk/2,end_json_response/1,
    start_chunked_response/3, absolute_uri/2, send/2,
    start_response_length/4]).

-record(doc_query_args, {
    options = [],
    rev = nil,
    open_revs = [],
    show = nil
}).

% Database request handlers
handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
        db_url_handlers=DbUrlHandlers}=Req)->
    case {Method, RestParts} of
    {'PUT', []} ->
        create_db_req(Req, DbName);
    {'DELETE', []} ->
        delete_db_req(Req, DbName);
    {_, []} ->
        do_db_req(Req, fun db_req/2);
    {_, [SecondPart|_]} ->
        Handler = couch_util:get_value(SecondPart, DbUrlHandlers, fun db_req/2),
        do_db_req(Req, Handler)
    end.

handle_changes_req(#httpd{method='GET'}=Req, Db) ->
    #changes_args{filter=Filter} = Args0 = parse_changes_query(Req),
    ChangesArgs = Args0#changes_args{filter=make_filter_fun(Filter, Req, Db)},
    case ChangesArgs#changes_args.feed of
    "normal" ->
        T0 = now(),
        {ok, Info} = fabric:get_db_info(Db),
        Etag = chttpd:make_etag(Info),
        DeltaT = timer:now_diff(now(), T0) / 1000,
        couch_stats_collector:record({couchdb, dbinfo}, DeltaT),
        chttpd:etag_respond(Req, Etag, fun() ->
            {ok, Resp} = chttpd:start_json_response(Req, 200, [{"Etag",Etag}]),
            fabric:changes(Db, ChangesArgs, fun changes_callback/2,
                {"normal", Resp})
        end);
    Feed ->
        % "longpoll" or "continuous"
        {ok, Resp} = chttpd:start_json_response(Req, 200),
        fabric:changes(Db, ChangesArgs, fun changes_callback/2, {Feed, Resp})
    end;
handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
    send_method_not_allowed(Req, "GET,HEAD").

% callbacks for continuous feed (newline-delimited JSON Objects)
changes_callback(start, {"continuous", _} = Acc) ->
    {ok, Acc};
changes_callback({change, Change}, {"continuous", Resp} = Acc) ->
    send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]),
    {ok, Acc};
changes_callback({stop, EndSeq0}, {"continuous", Resp}) ->
    EndSeq = case is_old_couch(Resp) of true -> 0; false -> EndSeq0 end,
    send_chunk(Resp, [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]),
    end_json_response(Resp);

% callbacks for longpoll and normal (single JSON Object)
changes_callback(start, {_, Resp}) ->
    send_chunk(Resp, "{\"results\":[\n"),
    {ok, {"", Resp}};
changes_callback({change, Change}, {Prepend, Resp}) ->
    send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]),
    {ok, {",\r\n", Resp}};
changes_callback({stop, EndSeq}, {_, Resp}) ->
    case is_old_couch(Resp) of
    true ->
        send_chunk(Resp, "\n],\n\"last_seq\":0}\n");
    false ->
        send_chunk(Resp, io_lib:format("\n],\n\"last_seq\":\"~s\"}\n",[EndSeq]))
    end,
    end_json_response(Resp);

changes_callback(timeout, {Prepend, Resp}) ->
    send_chunk(Resp, "\n"),
    {ok, {Prepend, Resp}};
changes_callback({error, Reason}, Resp) ->
    chttpd:send_chunked_error(Resp, {error, Reason}).

is_old_couch(Resp) ->
    MochiReq = Resp:get(request),
    case MochiReq:get_header_value("user-agent") of
    undefined ->
        false;
    UserAgent ->
        string:str(UserAgent, "CouchDB/0") > 0
    end.

handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
    ok = ?COUCH:compact_view_group(DbName, Id),
    send_json(Req, 202, {[{ok, true}]});

handle_compact_req(Req, _) ->
    Msg = <<"Compaction is handled automatically by Cloudant">>,
    chttpd:send_error(Req, 403, Msg).

handle_view_cleanup_req(Req, _) ->
    Msg = <<"Old view indices are purged automatically by Cloudant">>,
    chttpd:send_error(Req, 403, Msg).

handle_design_req(#httpd{
        path_parts=[_DbName, _Design, Name, <<"_",_/binary>> = Action | _Rest],
        design_url_handlers = DesignUrlHandlers
    }=Req, Db) ->
    case fabric:open_doc(Db, <<"_design/", Name/binary>>, []) of
    {ok, DDoc} ->
        % TODO we'll trigger a badarity here if ddoc attachment starts with "_",
        % or if user tries an unknown Action
        Handler = couch_util:get_value(Action, DesignUrlHandlers, fun db_req/2),
        Handler(Req, Db, DDoc);
    Error ->
        throw(Error)
    end;

handle_design_req(Req, Db) ->
    db_req(Req, Db).

handle_design_info_req(#httpd{method='GET'}=Req, Db, #doc{id=Id} = DDoc) ->
    {ok, GroupInfoList} = fabric:get_view_group_info(Db, DDoc),
    send_json(Req, 200, {[
        {name,  Id},
        {view_index, {GroupInfoList}}
    ]});

handle_design_info_req(Req, _Db, _DDoc) ->
    send_method_not_allowed(Req, "GET").

create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
    N = chttpd:qs_value(Req, "n", couch_config:get("cluster", "n", "3")),
    Q = chttpd:qs_value(Req, "q", couch_config:get("cluster", "q", "8")),
    case fabric:create_db(DbName, [{user_ctx, UserCtx},{n,N},{q,Q}]) of
    ok ->
        DocUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)),
        send_json(Req, 201, [{"Location", DocUrl}], {[{ok, true}]});
    {error, file_exists} ->
        chttpd:send_error(Req, file_exists);
    Error ->
        throw(Error)
    end.

{'fraction_non_alphanumeric': 0.16105176663927692, 'fraction_numerical': 0.01084634346754314, 'mean_word_length': 3.7710701685613484, 'pattern_counts': {'":': 6, '<': 27, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4180747', 'n_tokens_mistral': 4504, 'n_tokens_neox': 4301, 'n_words': 874}