diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-01-18 13:07:13 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-18 13:07:13 -0600 |
commit | 0c591905e1a31db0e6c34f35dd0b66c72ad03db0 (patch) | |
tree | 90a7e0b4624c055f3b0326e37a08452fc0c6fccf | |
parent | 1bdca62752f03db145d14252d9c233ffcbce779c (diff) | |
parent | 389330120de5f5a652400cb66ce657b5110dfe67 (diff) | |
download | couchdb-0c591905e1a31db0e6c34f35dd0b66c72ad03db0.tar.gz |
Merge pull request #34 from cloudant-labs/feature/database-partitions
Implement partitioned queries
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | include/dreyfus.hrl | 1 | ||||
-rw-r--r-- | src/clouseau_rpc.erl | 11 | ||||
-rw-r--r-- | src/dreyfus_httpd.erl | 108 | ||||
-rw-r--r-- | src/dreyfus_index.erl | 38 | ||||
-rw-r--r-- | src/dreyfus_index_updater.erl | 36 | ||||
-rw-r--r-- | src/dreyfus_util.erl | 80 | ||||
-rw-r--r-- | test/elixir/mix.exs | 30 | ||||
-rw-r--r-- | test/elixir/mix.lock | 5 | ||||
-rwxr-xr-x | test/elixir/run | 4 | ||||
-rw-r--r-- | test/elixir/test/partition_search_test.exs | 187 | ||||
-rw-r--r-- | test/elixir/test/test_helper.exs | 4 |
12 files changed, 431 insertions, 75 deletions
diff --git a/.gitignore b/.gitignore index 4598aa522..16fd00698 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ ebin/ .*.sw? +test/elixir/_build +test/elixir/deps diff --git a/include/dreyfus.hrl b/include/dreyfus.hrl index f86287c92..7c6a36945 100644 --- a/include/dreyfus.hrl +++ b/include/dreyfus.hrl @@ -32,6 +32,7 @@ -record(index_query_args, { q, + partition=nil, limit=25, stale=false, include_docs=false, diff --git a/src/clouseau_rpc.erl b/src/clouseau_rpc.erl index 345b499e6..38bf651d4 100644 --- a/src/clouseau_rpc.erl +++ b/src/clouseau_rpc.erl @@ -18,8 +18,8 @@ -include("dreyfus.hrl"). -export([open_index/3]). --export([await/2, commit/2, get_update_seq/1, info/1, search/6, search/2]). --export([group1/7, group2/8, group2/2]). +-export([await/2, commit/2, get_update_seq/1, info/1, search/2]). +-export([group1/7, group2/2]). -export([delete/2, update/3, cleanup/1, cleanup/2, rename/1]). -export([analyze/2, version/0, disk_size/1]). -export([set_purge_seq/2, get_purge_seq/1, get_root_dir/0]). @@ -50,10 +50,6 @@ set_purge_seq(Ref, Seq) -> get_purge_seq(Ref) -> rpc(Ref, get_purge_seq). -%% @deprecated -search(Ref, Query, Limit, Refresh, Bookmark, Sort) -> - rpc(Ref, {search, Query, Limit, Refresh, Bookmark, Sort}). - search(Ref, Args) -> case rpc(Ref, {search, Args}) of {ok, Response} when is_list(Response) -> @@ -71,9 +67,6 @@ search(Ref, Args) -> group1(Ref, Query, GroupBy, Refresh, Sort, Offset, Limit) -> rpc(Ref, {group1, Query, GroupBy, Refresh, Sort, Offset, Limit}). -group2(Ref, Query, GroupBy, Refresh, Groups, GroupSort, DocSort, DocLimit) -> - rpc(Ref, {group2, Query, GroupBy, Refresh, Groups, GroupSort, DocSort, DocLimit}). - group2(Ref, Args) -> rpc(Ref, {group2, Args}). diff --git a/src/dreyfus_httpd.erl b/src/dreyfus_httpd.erl index 8db545466..a1a5cb398 100644 --- a/src/dreyfus_httpd.erl +++ b/src/dreyfus_httpd.erl @@ -17,6 +17,7 @@ -export([handle_search_req/3, handle_info_req/3, handle_disk_size_req/3, handle_cleanup_req/2, handle_analyze_req/1]). + -include("dreyfus.hrl"). -include_lib("couch/include/couch_db.hrl"). -import(chttpd, [send_method_not_allowed/2, send_json/2, send_json/3, @@ -31,17 +32,10 @@ handle_search_req(#httpd{method=Method, path_parts=[_, _, _, _, IndexName]}=Req DbName = couch_db:name(Db), Start = os:timestamp(), QueryArgs = #index_query_args{ - q = Query, include_docs = IncludeDocs, grouping = Grouping - } = parse_index_params(Req), - case Query of - undefined -> - Msg = <<"Query must include a 'q' or 'query' argument">>, - throw({query_parse_error, Msg}); - _ -> - ok - end, + } = parse_index_params(Req, Db), + validate_search_restrictions(Db, DDoc, QueryArgs), Response = case Grouping#grouping.by of nil -> case dreyfus_fabric_search:go(DbName, DDoc, IndexName, QueryArgs) of @@ -190,22 +184,30 @@ analyze(Req, Analyzer, Text) -> send_error(Req, Reason) end. -parse_index_params(#httpd{method='GET'}=Req) -> +parse_index_params(#httpd{method='GET'}=Req, Db) -> IndexParams = lists:flatmap(fun({K, V}) -> parse_index_param(K, V) end, chttpd:qs(Req)), - parse_index_params(IndexParams); -parse_index_params(#httpd{method='POST'}=Req) -> + parse_index_params(IndexParams, Db); +parse_index_params(#httpd{method='POST'}=Req, Db) -> IndexParams = lists:flatmap(fun({K, V}) -> parse_json_index_param(K, V) end, element(1, chttpd:json_body_obj(Req))), - parse_index_params(IndexParams); -parse_index_params(IndexParams) -> - Args = #index_query_args{}, + parse_index_params(IndexParams, Db); +parse_index_params(IndexParams, Db) -> + DefaultLimit = case fabric_util:is_partitioned(Db) of + true -> + list_to_integer(config:get("dreyfus", "limit_partitions", "2000")); + false -> + list_to_integer(config:get("dreyfus", "limit", "25")) + end, + Args = #index_query_args{limit=DefaultLimit}, lists:foldl(fun({K, V}, Args2) -> validate_index_query(K, V, Args2) end, Args, IndexParams). validate_index_query(q, Value, Args) -> Args#index_query_args{q=Value}; +validate_index_query(partition, Value, Args) -> + Args#index_query_args{partition=Value}; validate_index_query(stale, Value, Args) -> Args#index_query_args{stale=Value}; validate_index_query(limit, Value, Args) -> @@ -254,12 +256,14 @@ parse_index_param("q", Value) -> [{q, ?l2b(Value)}]; parse_index_param("query", Value) -> [{q, ?l2b(Value)}]; +parse_index_param("partition", Value) -> + [{partition, ?l2b(Value)}]; parse_index_param("bookmark", Value) -> [{bookmark, ?l2b(Value)}]; parse_index_param("sort", Value) -> [{sort, ?JSON_DECODE(Value)}]; parse_index_param("limit", Value) -> - [{limit, parse_non_negative_int_param("limit", Value, "max_limit", "200")}]; + [{limit, ?JSON_DECODE(Value)}]; parse_index_param("stale", "ok") -> [{stale, ok}]; parse_index_param("stale", _Value) -> @@ -301,12 +305,14 @@ parse_json_index_param(<<"q">>, Value) -> [{q, Value}]; parse_json_index_param(<<"query">>, Value) -> [{q, Value}]; +parse_json_index_param(<<"partition">>, Value) -> + [{partition, Value}]; parse_json_index_param(<<"bookmark">>, Value) -> [{bookmark, Value}]; parse_json_index_param(<<"sort">>, Value) -> [{sort, Value}]; parse_json_index_param(<<"limit">>, Value) -> - [{limit, parse_non_negative_int_param("limit", Value, "max_limit", "200")}]; + [{limit, ?JSON_DECODE(Value)}]; parse_json_index_param(<<"stale">>, <<"ok">>) -> [{stale, ok}]; parse_json_index_param(<<"include_docs">>, Value) when is_boolean(Value) -> @@ -418,6 +424,74 @@ parse_non_negative_int_param(Name, Val, Prop, Default) -> end. +validate_search_restrictions(Db, DDoc, Args) -> + #index_query_args{ + q = Query, + partition = Partition, + grouping = Grouping, + limit = Limit + } = Args, + #grouping{ + by = GroupBy + } = Grouping, + + case Query of + undefined -> + Msg1 = <<"Query must include a 'q' or 'query' argument">>, + throw({query_parse_error, Msg1}); + _ -> + ok + end, + + DbPartitioned = fabric_util:is_partitioned(Db), + ViewPartitioned = get_view_partition_option(DDoc, DbPartitioned), + + case not DbPartitioned andalso is_binary(Partition) of + true -> + Msg2 = <<"`partition` not supported on this index">>, + throw({bad_request, Msg2}); + false -> + ok + end, + + case {ViewPartitioned, is_binary(Partition)} of + {false, false} -> + ok; + {true, true} -> + ok; + {true, false} -> + Msg3 = <<"`partition` parameter is mandatory " + "for queries to this index.">>, + throw({bad_request, Msg3}); + {false, true} -> + Msg4 = <<"`partition` not supported on this index">>, + throw({bad_request, Msg4}) + end, + + case DbPartitioned of + true -> + MaxLimit = config:get("dreyfus", "max_limit", "2000"), + parse_non_negative_int_param( + "limit", Limit, "max_limit_partitions", MaxLimit); + false -> + MaxLimit = config:get("dreyfus", "max_limit", "200"), + parse_non_negative_int_param("limit", Limit, "max_limit", MaxLimit) + end, + + case GroupBy /= nil andalso is_binary(Partition) of + true -> + Msg5 = <<"`group_by` and `partition` are incompatible">>, + throw({bad_request, Msg5}); + false -> + ok + end. + + +get_view_partition_option(#doc{body = {Props}}, Default) -> + {Options} = couch_util:get_value(<<"options">>, Props, {[]}), + couch_util:get_value(<<"partitioned">>, Options, Default). + + hits_to_json(DbName, IncludeDocs, Hits) -> {Ids, HitData} = lists:unzip(lists:map(fun get_hit_data/1, Hits)), if IncludeDocs -> diff --git a/src/dreyfus_index.erl b/src/dreyfus_index.erl index c6d4d856a..e33a208ee 100644 --- a/src/dreyfus_index.erl +++ b/src/dreyfus_index.erl @@ -305,6 +305,7 @@ index_name(#index{dbname=DbName,ddoc_id=DDocId,name=IndexName}) -> args_to_proplist(#index_query_args{} = Args) -> [ {'query', Args#index_query_args.q}, + {partition, Args#index_query_args.partition}, {limit, Args#index_query_args.limit}, {refresh, Args#index_query_args.stale =:= false}, {'after', Args#index_query_args.bookmark}, @@ -339,20 +340,8 @@ args_to_proplist2(#index_query_args{} = Args) -> search_int(Pid, QueryArgs0) -> QueryArgs = dreyfus_util:upgrade(QueryArgs0), - case QueryArgs of - #index_query_args{counts=nil,ranges=nil,drilldown=[],include_fields=nil, - highlight_fields=nil} -> - clouseau_rpc:search( - Pid, - QueryArgs#index_query_args.q, - QueryArgs#index_query_args.limit, - QueryArgs#index_query_args.stale =:= false, - QueryArgs#index_query_args.bookmark, - QueryArgs#index_query_args.sort); - _ -> - Props = args_to_proplist(QueryArgs), - clouseau_rpc:search(Pid, Props) - end. + Props = args_to_proplist(QueryArgs), + clouseau_rpc:search(Pid, Props). group1_int(Pid, QueryArgs0) -> QueryArgs = dreyfus_util:upgrade(QueryArgs0), @@ -371,25 +360,8 @@ group1_int(Pid, QueryArgs0) -> group2_int(Pid, QueryArgs0) -> QueryArgs = dreyfus_util:upgrade(QueryArgs0), - case QueryArgs of - #index_query_args{include_fields=nil, highlight_fields=nil} -> %remove after upgrade - #index_query_args{ - q = Query, - stale = Stale, - sort = DocSort, - limit = DocLimit, - grouping = #grouping{ - by = GroupBy, - groups = Groups, - sort = GroupSort - } - } = QueryArgs, - clouseau_rpc:group2(Pid, Query, GroupBy, Stale =:= false, Groups, - GroupSort, DocSort, DocLimit); - _ -> - Props = args_to_proplist2(QueryArgs), - clouseau_rpc:group2(Pid, Props) - end. + Props = args_to_proplist2(QueryArgs), + clouseau_rpc:group2(Pid, Props). info_int(Pid) -> clouseau_rpc:info(Pid). diff --git a/src/dreyfus_index_updater.erl b/src/dreyfus_index_updater.erl index e2fbe2b05..40fd0c377 100644 --- a/src/dreyfus_index_updater.erl +++ b/src/dreyfus_index_updater.erl @@ -132,13 +132,19 @@ update_or_delete_index(IndexPid, Db, DI, Proc) -> true -> ok = clouseau_rpc:delete(IndexPid, Id); false -> - {ok, Doc} = couch_db:open_doc(Db, DI, []), - Json = couch_doc:to_json_obj(Doc, []), - [Fields|_] = proc_prompt(Proc, [<<"index_doc">>, Json]), - Fields1 = [list_to_tuple(Field) || Field <- Fields], - case Fields1 of - [] -> ok = clouseau_rpc:delete(IndexPid, Id); - _ -> ok = clouseau_rpc:update(IndexPid, Id, Fields1) + case maybe_skip_doc(Db, Id) of + true -> + ok; + false -> + {ok, Doc} = couch_db:open_doc(Db, DI, []), + Json = couch_doc:to_json_obj(Doc, []), + [Fields|_] = proc_prompt(Proc, [<<"index_doc">>, Json]), + Fields1 = [list_to_tuple(Field) || Field <- Fields], + Fields2 = maybe_add_partition(Db, Id, Fields1), + case Fields2 of + [] -> ok = clouseau_rpc:delete(IndexPid, Id); + _ -> ok = clouseau_rpc:update(IndexPid, Id, Fields2) + end end end. @@ -157,3 +163,19 @@ update_task(NumChanges) -> (Changes2 * 100) div Total end, couch_task_status:update([{progress, Progress}, {changes_done, Changes2}]). + +maybe_skip_doc(Db, <<"_design/", _/binary>>) -> + couch_db:is_partitioned(Db); +maybe_skip_doc(_Db, _Id) -> + false. + +maybe_add_partition(_Db, _Id, []) -> + []; +maybe_add_partition(Db, Id, Fields) -> + case couch_db:is_partitioned(Db) of + true -> + Partition = couch_partition:from_docid(Id), + [{<<"_partition">>, Partition, {[]}} | Fields]; + false -> + Fields + end. diff --git a/src/dreyfus_util.erl b/src/dreyfus_util.erl index 3b3f4f955..ae3133e7d 100644 --- a/src/dreyfus_util.erl +++ b/src/dreyfus_util.erl @@ -33,15 +33,31 @@ verify_index_exists/2 ]). -get_shards(DbName, #index_query_args{stale=ok}) -> - mem3:ushards(DbName); -get_shards(DbName, #index_query_args{stable=true}) -> - mem3:ushards(DbName); -get_shards(DbName, #index_query_args{stale=false}) -> - mem3:shards(DbName); + +get_shards(DbName, #index_query_args{partition = nil} = Args) -> + case use_ushards(Args) of + true -> + mem3:ushards(DbName); + false -> + mem3:shards(DbName) + end; +get_shards(DbName, #index_query_args{partition = Partition} = Args) -> + PartitionId = couch_partition:shard_key(Partition), + case use_ushards(Args) of + true -> + mem3:ushards(DbName, PartitionId); + false -> + mem3:shards(DbName, PartitionId) + end; get_shards(DbName, Args) -> get_shards(DbName, upgrade(Args)). +use_ushards(#index_query_args{stale=ok}) -> + true; +use_ushards(#index_query_args{stable=true}) -> + true; +use_ushards(#index_query_args{}) -> + false. -spec sort(Order :: relevance | [any()], [#sortable{}]) -> [#sortable{}]. sort(Sort, List0) -> @@ -136,10 +152,34 @@ upgrade({index_query_args, Query, Limit, Stale, IncludeDocs, Bookmark, highlight_post_tag = HighlightPostTag, highlight_number = HighlightNumber, highlight_size = HighlightSize + }; +upgrade({index_query_args, Query, Limit, Stale, IncludeDocs, Bookmark, + Sort, Grouping, Stable, Counts, Ranges, Drilldown, + IncludeFields, HighlightFields, HighlightPreTag, HighlightPostTag, + HighlightNumber, HighlightSize, RawBookmark}) -> + #index_query_args{ + q = Query, + limit = Limit, + stale = Stale, + include_docs = IncludeDocs, + bookmark = Bookmark, + sort = Sort, + grouping = Grouping, + stable = Stable, + counts = Counts, + ranges = Ranges, + drilldown = Drilldown, + include_fields = IncludeFields, + highlight_fields = HighlightFields, + highlight_pre_tag = HighlightPreTag, + highlight_post_tag = HighlightPostTag, + highlight_number = HighlightNumber, + highlight_size = HighlightSize, + raw_bookmark = RawBookmark }. -export(#index_query_args{counts = nil, ranges = nil, drilldown = [], - include_fields = nil, highlight_fields = nil} = Args) -> +export(#index_query_args{partition = nil, counts = nil, ranges = nil, + drilldown = [], include_fields = nil, highlight_fields = nil} = Args) -> % Ensure existing searches work during the upgrade by creating an % #index_query_args record in the old format {index_query_args, @@ -152,7 +192,8 @@ export(#index_query_args{counts = nil, ranges = nil, drilldown = [], Args#index_query_args.grouping, Args#index_query_args.stable }; -export(#index_query_args{include_fields = nil, highlight_fields = nil} = Args) -> +export(#index_query_args{partition = nil, include_fields = nil, + highlight_fields = nil} = Args) -> {index_query_args, Args#index_query_args.q, Args#index_query_args.limit, @@ -166,6 +207,27 @@ export(#index_query_args{include_fields = nil, highlight_fields = nil} = Args) - Args#index_query_args.ranges, Args#index_query_args.drilldown }; +export(#index_query_args{partition = nil} = Args) -> + {index_query_args, + Args#index_query_args.q, + Args#index_query_args.limit, + Args#index_query_args.stale, + Args#index_query_args.include_docs, + Args#index_query_args.bookmark, + Args#index_query_args.sort, + Args#index_query_args.grouping, + Args#index_query_args.stable, + Args#index_query_args.counts, + Args#index_query_args.ranges, + Args#index_query_args.drilldown, + Args#index_query_args.include_fields, + Args#index_query_args.highlight_fields, + Args#index_query_args.highlight_pre_tag, + Args#index_query_args.highlight_post_tag, + Args#index_query_args.highlight_number, + Args#index_query_args.highlight_size, + Args#index_query_args.raw_bookmark + }; export(QueryArgs) -> QueryArgs. diff --git a/test/elixir/mix.exs b/test/elixir/mix.exs new file mode 100644 index 000000000..9b0f642dd --- /dev/null +++ b/test/elixir/mix.exs @@ -0,0 +1,30 @@ +defmodule Foo.Mixfile do + use Mix.Project + + def project do + [ + app: :foo, + version: "0.1.0", + elixir: "~> 1.5", + start_permanent: Mix.env == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + {:httpotion, "~> 3.0"}, + {:jiffy, "~> 0.14.11"} + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}, + ] + end +end diff --git a/test/elixir/mix.lock b/test/elixir/mix.lock new file mode 100644 index 000000000..ed51e5312 --- /dev/null +++ b/test/elixir/mix.lock @@ -0,0 +1,5 @@ +%{ + "httpotion": {:hex, :httpotion, "3.1.0", "14d20d9b0ce4e86e253eb91e4af79e469ad949f57a5d23c0a51b2f86559f6589", [:mix], [{:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm"}, + "ibrowse": {:hex, :ibrowse, "4.4.1", "2b7d0637b0f8b9b4182de4bd0f2e826a4da2c9b04898b6e15659ba921a8d6ec2", [:rebar3], [], "hexpm"}, + "jiffy": {:hex, :jiffy, "0.14.13", "225a9a35e26417832c611526567194b4d3adc4f0dfa5f2f7008f4684076f2a01", [:rebar3], [], "hexpm"}, +} diff --git a/test/elixir/run b/test/elixir/run new file mode 100755 index 000000000..66a5947b7 --- /dev/null +++ b/test/elixir/run @@ -0,0 +1,4 @@ +#!/bin/bash -e +cd "$(dirname "$0")" +mix deps.get +mix test --trace diff --git a/test/elixir/test/partition_search_test.exs b/test/elixir/test/partition_search_test.exs new file mode 100644 index 000000000..98b23b508 --- /dev/null +++ b/test/elixir/test/partition_search_test.exs @@ -0,0 +1,187 @@ +defmodule PartitionSearchTest do + use CouchTestCase + + @moduletag :search + + @moduledoc """ + Test Partition functionality with search + """ + + def create_search_docs(db_name, pk1 \\ "foo", pk2 \\ "bar") do + docs = for i <- 1..10 do + id = if rem(i, 2) == 0 do + "#{pk1}:#{i}" + else + "#{pk2}:#{i}" + end + %{ + :_id => id, + :value => i, + :some => "field" + } + end + + resp = Couch.post("/#{db_name}/_bulk_docs", body: %{:docs => docs} ) + assert resp.status_code == 201 + end + + def create_ddoc(db_name, opts \\ %{}) do + indexFn = "function(doc) {\n if (doc.some) {\n index('some', doc.some);\n }\n}" + default_ddoc = %{ + indexes: %{ + books: %{ + analyzer: %{name: "standard"}, + index: indexFn + } + } + } + + ddoc = Enum.into(opts, default_ddoc) + + resp = Couch.put("/#{db_name}/_design/library", body: ddoc) + assert resp.status_code == 201 + assert Map.has_key?(resp.body, "ok") == true + end + + def get_ids (resp) do + %{:body => %{"rows" => rows}} = resp + Enum.map(rows, fn row -> row["id"] end) + end + + @tag :with_partitioned_db + test "Simple query returns partitioned search results", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:10", "foo:2", "foo:4", "foo:6", "foo:8"] + + url = "/#{db_name}/_partition/bar/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["bar:1", "bar:3", "bar:5", "bar:7", "bar:9"] + end + + @tag :with_partitioned_db + test "Only returns docs in partition not those in shard", context do + db_name = context[:db_name] + create_search_docs(db_name, "foo", "bar42") + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:10", "foo:2", "foo:4", "foo:6", "foo:8"] + end + + @tag :with_partitioned_db + test "Works with bookmarks and limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field", limit: 3}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:10", "foo:2", "foo:4"] + + %{:body => %{"bookmark" => bookmark}} = resp + + resp = Couch.get(url, query: %{q: "some:field", limit: 3, bookmark: bookmark}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:6", "foo:8"] + + resp = Couch.get(url, query: %{q: "some:field", limit: 2000, bookmark: bookmark}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["foo:6", "foo:8"] + + resp = Couch.get(url, query: %{q: "some:field", limit: 2001, bookmark: bookmark}) + assert resp.status_code == 400 + end + + @tag :with_partitioned_db + test "Cannot do global query with partition view", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 400 + %{:body => %{"reason" => reason}} = resp + assert Regex.match?(~r/mandatory for queries to this index./, reason) + end + + @tag :with_partitioned_db + test "Cannot do partition query with global search ddoc", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name, options: %{partitioned: false}) + + url = "/#{db_name}/_partition/foo/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 400 + %{:body => %{"reason" => reason}} = resp + assert reason == "`partition` not supported on this index" + end + + @tag :with_db + test "normal search on non-partitioned dbs still work", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["bar:1", "bar:5", "bar:9", "foo:2", "bar:3", "foo:4", "foo:6", "bar:7", "foo:8", "foo:10"] + end + + @tag :with_db + test "normal search on non-partitioned dbs without limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field"}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["bar:1", "bar:5", "bar:9", "foo:2", "bar:3", "foo:4", "foo:6", "bar:7", "foo:8", "foo:10"] + end + + @tag :with_db + test "normal search on non-partitioned dbs with limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field", limit: 3}) + assert resp.status_code == 200 + ids = get_ids(resp) + assert ids == ["bar:1", "bar:5", "bar:9"] + end + + @tag :with_db + test "normal search on non-partitioned dbs with over limit", context do + db_name = context[:db_name] + create_search_docs(db_name) + create_ddoc(db_name) + + url = "/#{db_name}/_design/library/_search/books" + resp = Couch.get(url, query: %{q: "some:field", limit: 201}) + assert resp.status_code == 400 + end + +end diff --git a/test/elixir/test/test_helper.exs b/test/elixir/test/test_helper.exs new file mode 100644 index 000000000..6eb20e242 --- /dev/null +++ b/test/elixir/test/test_helper.exs @@ -0,0 +1,4 @@ +Code.require_file "../../../../couchdb/test/elixir/lib/couch.ex", __DIR__ +Code.require_file "../../../../couchdb/test/elixir/test/test_helper.exs", __DIR__ +Code.require_file "../../../../couchdb/test/elixir/test/support/couch_test_case.ex", __DIR__ +Code.require_file "../../../../couchdb/test/elixir/lib/couch/db_test.ex", __DIR__ |