You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2019/01/10 19:39:39 UTC

[couchdb] branch feature/database-partitions updated (31a4a53 -> 2635741)

This is an automated email from the ASF dual-hosted git repository.

davisp pushed a change to branch feature/database-partitions
in repository https://gitbox.apache.org/repos/asf/couchdb.git.


    omit 31a4a53  Add Elixir tests for database partitions
     new 2635741  Add Elixir tests for database partitions

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (31a4a53)
            \
             N -- N -- N   refs/heads/feature/database-partitions (2635741)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 test/elixir/test/partition_ddoc_test.exs | 171 +++++++++++++++++++
 test/elixir/test/partition_size_test.exs | 282 ++++++++++++++++++++++---------
 2 files changed, 377 insertions(+), 76 deletions(-)
 create mode 100644 test/elixir/test/partition_ddoc_test.exs


[couchdb] 01/01: Add Elixir tests for database partitions

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

davisp pushed a commit to branch feature/database-partitions
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 2635741ef15d609836b491adc4f35b2bd3fa9348
Author: Paul J. Davis <pa...@gmail.com>
AuthorDate: Wed Nov 28 10:58:42 2018 -0600

    Add Elixir tests for database partitions
    
    Co-authored-by: Garren Smith <ga...@gmail.com>
    Co-authored-by: Robert Newson <rn...@apache.org>
---
 test/elixir/lib/couch/db_test.ex                |  12 +-
 test/elixir/test/partition_crud_test.exs        | 320 +++++++++++++
 test/elixir/test/partition_ddoc_test.exs        | 171 +++++++
 test/elixir/test/partition_design_docs_test.exs |  16 +
 test/elixir/test/partition_helpers.exs          |  76 +++
 test/elixir/test/partition_mango_test.exs       | 591 ++++++++++++++++++++++++
 test/elixir/test/partition_size_test.exs        | 265 +++++++++++
 test/elixir/test/partition_view_test.exs        | 299 ++++++++++++
 test/elixir/test/partition_view_update_test.exs |  73 +++
 test/elixir/test/test_helper.exs                |   1 +
 10 files changed, 1821 insertions(+), 3 deletions(-)

diff --git a/test/elixir/lib/couch/db_test.ex b/test/elixir/lib/couch/db_test.ex
index 8992376..ba65a6d 100644
--- a/test/elixir/lib/couch/db_test.ex
+++ b/test/elixir/lib/couch/db_test.ex
@@ -18,6 +18,12 @@ defmodule Couch.DBTest do
           |> Map.put(:db_name, random_db_name(db_name))
           |> Map.put(:with_db, true)
 
+        %{:with_partitioned_db => true} ->
+          context
+          |> Map.put(:db_name, random_db_name())
+          |> Map.put(:query, %{partitioned: true})
+          |> Map.put(:with_db, true)
+
         %{:with_db => true} ->
           Map.put(context, :db_name, random_db_name())
 
@@ -29,7 +35,7 @@ defmodule Couch.DBTest do
       end
 
     if Map.has_key?(context, :with_db) do
-      {:ok, _} = create_db(context[:db_name])
+      {:ok, _} = create_db(context[:db_name], query: context[:query])
       on_exit(fn -> delete_db(context[:db_name]) end)
     end
 
@@ -154,8 +160,8 @@ defmodule Couch.DBTest do
     Map.put(user_doc, "_rev", resp.body["rev"])
   end
 
-  def create_db(db_name) do
-    resp = Couch.put("/#{db_name}")
+  def create_db(db_name, opts \\ []) do
+    resp = Couch.put("/#{db_name}", opts)
     assert resp.status_code in [201, 202]
     assert resp.body == %{"ok" => true}
     {:ok, resp}
diff --git a/test/elixir/test/partition_crud_test.exs b/test/elixir/test/partition_crud_test.exs
new file mode 100644
index 0000000..fc3af4a
--- /dev/null
+++ b/test/elixir/test/partition_crud_test.exs
@@ -0,0 +1,320 @@
+defmodule PartitionCrudTest do
+  use CouchTestCase
+
+  @tag :with_partitioned_db
+  test "Sets partition in db info", context do
+    db_name = context[:db_name]
+    resp = Couch.get("/#{db_name}")
+    %{body: body} = resp
+    assert body["props"] == %{"partitioned" => true}
+  end
+
+  @tag :with_partitioned_db
+  test "PUT and GET document", context do
+    db_name = context[:db_name]
+    id = "my-partition:doc"
+    url = "/#{db_name}/#{id}"
+
+    resp = Couch.put(url, body: %{partitioned_doc: true})
+    %{body: doc} = resp
+    assert resp.status_code == 201
+    assert doc["id"] == id
+
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+
+    %{body: doc} = resp
+    assert doc["_id"] == id
+  end
+
+  @tag :with_partitioned_db
+  test "PUT fails if a partition key is not supplied", context do
+    db_name = context[:db_name]
+    id = "not-partitioned"
+    url = "/#{db_name}/#{id}"
+
+    resp = Couch.put(url, body: %{partitioned_doc: false})
+    assert resp.status_code == 400
+
+    error = %{
+      "error" => "illegal_docid",
+      "reason" => "Doc id must be of form partition:id"
+    }
+
+    assert Map.get(resp, :body) == error
+  end
+
+  @tag :with_partitioned_db
+  test "PUT fails for partitions with _", context do
+    db_name = context[:db_name]
+    id = "_bad:partitioned"
+    url = "/#{db_name}/#{id}"
+
+    resp = Couch.put(url, body: %{partitioned_doc: false})
+
+    error = %{
+      "error" => "illegal_docid",
+      "reason" => "Only reserved document ids may start with underscore."
+    }
+
+    assert resp.status_code == 400
+    assert Map.get(resp, :body) == error
+  end
+
+  @tag :with_partitioned_db
+  test "PUT fails for bad partitions", context do
+    db_name = context[:db_name]
+    id = "bad:"
+    url = "/#{db_name}/#{id}"
+
+    resp = Couch.put(url, body: %{partitioned_doc: false})
+
+    error = %{
+      "error" => "illegal_docid",
+      "reason" => "Document id must not be empty"
+    }
+
+    assert resp.status_code == 400
+    assert Map.get(resp, :body) == error
+  end
+
+  @tag :with_partitioned_db
+  test "POST and GET document", context do
+    db_name = context[:db_name]
+    id = "my-partition-post:doc"
+    url = "/#{db_name}"
+
+    resp = Couch.post(url, body: %{_id: id, partitioned_doc: true})
+    assert resp.status_code == 201
+
+    resp = Couch.get("#{url}/#{id}")
+    assert resp.status_code == 200
+
+    %{body: doc} = resp
+    assert doc["_id"] == id
+  end
+
+  @tag :with_partitioned_db
+  test "POST and _bulk_get document", context do
+    db_name = context[:db_name]
+    id = "my-partition-post:doc"
+    url = "/#{db_name}"
+
+    resp = Couch.post(url, body: %{_id: id, partitioned_doc: true})
+    assert resp.status_code == 201
+
+    resp = Couch.post("#{url}/_bulk_get", body: %{docs: [%{id: id}]})
+    assert resp.status_code == 200
+
+    %{body: body} = resp
+
+    assert %{
+             "results" => [
+               %{
+                 "docs" => [
+                   %{
+                     "ok" => %{
+                       "_id" => "my-partition-post:doc",
+                       "_rev" => "1-43d86359741cb629c0953a2beb6e9d7a",
+                       "partitioned_doc" => true
+                     }
+                   }
+                 ],
+                 "id" => "my-partition-post:doc"
+               }
+             ]
+           } == body
+  end
+
+  @tag :with_partitioned_db
+  test "_bulk_get bad partitioned document", context do
+    db_name = context[:db_name]
+    id = "my-partition-post"
+    url = "/#{db_name}"
+
+    resp = Couch.post("#{url}/_bulk_get", body: %{docs: [%{id: id}]})
+    assert resp.status_code == 200
+    %{:body => body} = resp
+
+    assert %{
+             "results" => [
+               %{
+                 "docs" => [
+                   %{
+                     "error" => %{
+                       "error" => "illegal_docid",
+                       "id" => "my-partition-post",
+                       "reason" => "Doc id must be of form partition:id",
+                       "rev" => :null
+                     }
+                   }
+                 ],
+                 "id" => "my-partition-post"
+               }
+             ]
+           } == body
+  end
+
+  @tag :with_partitioned_db
+  test "POST fails if a partition key is not supplied", context do
+    db_name = context[:db_name]
+    id = "not-partitioned-post"
+    url = "/#{db_name}"
+
+    resp = Couch.post(url, body: %{_id: id, partitited_doc: false})
+    assert resp.status_code == 400
+  end
+
+  @tag :with_partitioned_db
+  test "_bulk_docs saves docs with partition key", context do
+    db_name = context[:db_name]
+
+    docs = [
+      %{_id: "foo:1"},
+      %{_id: "bar:1"}
+    ]
+
+    url = "/#{db_name}"
+    resp = Couch.post("#{url}/_bulk_docs", body: %{:docs => docs})
+    assert resp.status_code == 201
+
+    resp = Couch.get("#{url}/foo:1")
+    assert resp.status_code == 200
+
+    resp = Couch.get("#{url}/bar:1")
+    assert resp.status_code == 200
+  end
+
+  @tag :with_partitioned_db
+  test "_bulk_docs errors with missing partition key", context do
+    db_name = context[:db_name]
+
+    docs = [
+      %{_id: "foo1"}
+    ]
+
+    error = %{
+      "error" => "illegal_docid",
+      "reason" => "Doc id must be of form partition:id"
+    }
+
+    url = "/#{db_name}"
+    resp = Couch.post("#{url}/_bulk_docs", body: %{:docs => docs})
+    assert resp.status_code == 400
+    assert Map.get(resp, :body) == error
+  end
+
+  @tag :with_partitioned_db
+  test "_bulk_docs errors with bad partition key", context do
+    db_name = context[:db_name]
+
+    docs = [
+      %{_id: "_foo:1"}
+    ]
+
+    error = %{
+      "error" => "illegal_docid",
+      "reason" => "Only reserved document ids may start with underscore."
+    }
+
+    url = "/#{db_name}"
+    resp = Couch.post("#{url}/_bulk_docs", body: %{:docs => docs})
+    assert resp.status_code == 400
+    assert Map.get(resp, :body) == error
+  end
+
+  @tag :with_partitioned_db
+  test "_bulk_docs errors with bad doc key", context do
+    db_name = context[:db_name]
+
+    docs = [
+      %{_id: "foo:"}
+    ]
+
+    error = %{
+      "error" => "illegal_docid",
+      "reason" => "Document id must not be empty"
+    }
+
+    url = "/#{db_name}"
+    resp = Couch.post("#{url}/_bulk_docs", body: %{:docs => docs})
+    assert resp.status_code == 400
+    assert Map.get(resp, :body) == error
+  end
+
+  @tag :with_partitioned_db
+  test "saves attachment with partitioned doc", context do
+    db_name = context[:db_name]
+    id = "foo:doc-with-attachment"
+
+    doc = %{
+      _id: id,
+      _attachments: %{
+        "foo.txt": %{
+          content_type: "text/plain",
+          data: Base.encode64("This is a text document to save")
+        }
+      }
+    }
+
+    resp = Couch.put("/#{db_name}/#{id}", body: doc)
+
+    assert resp.status_code == 201
+
+    resp = Couch.get("/#{db_name}/#{id}")
+    assert resp.status_code == 200
+    body = Map.get(resp, :body)
+    rev = Map.get(body, "_rev")
+
+    assert body["_attachments"] == %{
+             "foo.txt" => %{
+               "content_type" => "text/plain",
+               "digest" => "md5-OW2BoZAtMqs1E+fAnLpNBw==",
+               "length" => 31,
+               "revpos" => 1,
+               "stub" => true
+             }
+           }
+
+    resp = Couch.get("/#{db_name}/#{id}/foo.txt")
+    assert Map.get(resp, :body) == "This is a text document to save"
+
+    resp =
+      Couch.put("/#{db_name}/#{id}/bar.txt?rev=#{rev}",
+        headers: ["Content-Type": "text/plain"],
+        body: "This is another document"
+      )
+
+    assert resp.status_code == 201
+    %{:body => body} = resp
+    assert body["ok"] == true
+    assert body["id"] == id
+  end
+
+  test "create database with bad `partitioned` value", _context do
+    resp = Couch.put("/bad-db?partitioned=tru")
+    assert resp.status_code == 400
+
+    assert Map.get(resp, :body) == %{
+             "error" => "bad_request",
+             "reason" => "Invalid `partitioned` parameter"
+           }
+  end
+
+  test "can create unpartitioned system db", _context do
+    Couch.delete("/_replicator")
+    resp = Couch.put("/_replicator")
+    assert resp.status_code == 201
+    assert resp.body == %{"ok" => true}
+  end
+
+  test "cannot create partitioned system db", _context do
+    Couch.delete("/_replicator")
+
+    resp = Couch.put("/_replicator?partitioned=true")
+    assert resp.status_code == 400
+
+    %{:body => %{"reason" => reason}} = resp
+    assert Regex.match?(~r/Cannot partition a system database/, reason)
+  end
+end
diff --git a/test/elixir/test/partition_ddoc_test.exs b/test/elixir/test/partition_ddoc_test.exs
new file mode 100644
index 0000000..4b1f00d
--- /dev/null
+++ b/test/elixir/test/partition_ddoc_test.exs
@@ -0,0 +1,171 @@
+defmodule PartitionDDocTest do
+  use CouchTestCase
+
+  @moduledoc """
+  Test partition design doc interactions
+  """
+
+  setup do
+    db_name = random_db_name()
+    {:ok, _} = create_db(db_name, query: %{partitioned: true, q: 1})
+    on_exit(fn -> delete_db(db_name) end)
+
+    {:ok, [db_name: db_name]}
+  end
+
+  test "PUT /dbname/_design/foo", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+  end
+
+  test "PUT /dbname/_design/foo to update", context do
+    db_name = context[:db_name]
+    ddoc_id = "_design/foo"
+
+    ddoc = %{
+      _id: ddoc_id,
+      stuff: "here"
+    }
+
+    resp = Couch.put("/#{db_name}/#{ddoc_id}", body: ddoc)
+    assert resp.status_code == 201
+    %{body: body} = resp
+
+    ddoc = Map.put(ddoc, :_rev, body["rev"])
+    ddoc = Map.put(ddoc, :other, "attribute")
+    resp = Couch.put("/#{db_name}/#{ddoc_id}", body: ddoc)
+    assert resp.status_code == 201
+  end
+
+  test "PUT /dbname/_design/foo/readme.txt", context do
+    db_name = context[:db_name]
+    ddoc_id = "_design/foo"
+
+    ddoc = %{
+      _id: ddoc_id,
+      stuff: "here"
+    }
+
+    resp = Couch.put("/#{db_name}/#{ddoc_id}", body: ddoc)
+    assert resp.status_code == 201
+    %{body: body} = resp
+
+    att = "This is a readme.txt"
+
+    opts = [
+      headers: [{:"Content-Type", "text/plain"}],
+      query: [rev: body["rev"]],
+      body: att
+    ]
+
+    resp = Couch.put("/#{db_name}/#{ddoc_id}/readme.txt", opts)
+    assert resp.status_code == 201
+  end
+
+  test "DELETE /dbname/_design/foo", context do
+    db_name = context[:db_name]
+    ddoc_id = "_design/foo"
+
+    ddoc = %{
+      _id: ddoc_id,
+      stuff: "here"
+    }
+
+    resp = Couch.put("/#{db_name}/#{ddoc_id}", body: ddoc)
+    assert resp.status_code == 201
+    %{body: body} = resp
+
+    resp = Couch.delete("/#{db_name}/#{ddoc_id}", query: [rev: body["rev"]])
+    assert resp.status_code == 200
+  end
+
+  test "POST /dbname with design doc", context do
+    db_name = context[:db_name]
+    body = %{_id: "_design/foo", stuff: "here"}
+    resp = Couch.post("/#{db_name}", body: body)
+    assert resp.status_code == 201
+  end
+
+  test "POST /dbname/_bulk_docs with design doc", context do
+    db_name = context[:db_name]
+    body = %{:docs => [%{_id: "_design/foo", stuff: "here"}]}
+    resp = Couch.post("/#{db_name}/_bulk_docs", body: body)
+    assert resp.status_code == 201
+  end
+
+  test "GET /dbname/_design/foo", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+
+    resp = Couch.get("/#{db_name}/_design/foo")
+    assert resp.status_code == 200
+  end
+
+  test "GET /dbname/_design/foo?rev=$rev", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+    %{body: body} = resp
+
+    resp = Couch.get("/#{db_name}/_design/foo", query: [rev: body["rev"]])
+    assert resp.status_code == 200
+  end
+
+  test "GET /dbname/_bulk_get", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+
+    body = %{docs: [%{id: "_design/foo"}]}
+    resp = Couch.post("/#{db_name}/_bulk_get", body: body)
+    assert resp.status_code == 200
+    %{body: body} = resp
+
+    assert length(body["results"]) == 1
+
+    %{"results" => [%{"id" => "_design/foo", "docs" => [%{"ok" => _}]}]} = body
+  end
+
+  test "GET /dbname/_bulk_get with rev", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+    %{body: body} = resp
+
+    body = %{docs: [%{id: "_design/foo", rev: body["rev"]}]}
+    resp = Couch.post("/#{db_name}/_bulk_get", body: body)
+    assert resp.status_code == 200
+    %{body: body} = resp
+
+    assert length(body["results"]) == 1
+    %{"results" => [%{"id" => "_design/foo", "docs" => [%{"ok" => _}]}]} = body
+  end
+
+  test "GET /dbname/_all_docs?key=$ddoc_id", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+
+    resp = Couch.get("/#{db_name}/_all_docs", query: [key: "\"_design/foo\""])
+    assert resp.status_code == 200
+    %{body: body} = resp
+
+    assert length(body["rows"]) == 1
+    %{"rows" => [%{"id" => "_design/foo"}]} = body
+  end
+
+  test "GET /dbname/_design_docs", context do
+    db_name = context[:db_name]
+    resp = Couch.put("/#{db_name}/_design/foo", body: %{stuff: "here"})
+    assert resp.status_code == 201
+
+    resp = Couch.get("/#{db_name}/_design_docs")
+    assert resp.status_code == 200
+    %{body: body} = resp
+
+    assert length(body["rows"]) == 1
+    %{"rows" => [%{"id" => "_design/foo"}]} = body
+  end
+end
diff --git a/test/elixir/test/partition_design_docs_test.exs b/test/elixir/test/partition_design_docs_test.exs
new file mode 100644
index 0000000..42a2ced
--- /dev/null
+++ b/test/elixir/test/partition_design_docs_test.exs
@@ -0,0 +1,16 @@
+defmodule PartitionDesignDocsTest do
+  use CouchTestCase
+
+  @moduledoc """
+  Test Partition functionality for partition design docs
+  """
+
+  @tag :with_partitioned_db
+  test "/_partition/:pk/_design/doc 404", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/fake-key/_design/mrtest/"
+    resp = Couch.get(url)
+    assert resp.status_code == 404
+  end
+end
diff --git a/test/elixir/test/partition_helpers.exs b/test/elixir/test/partition_helpers.exs
new file mode 100644
index 0000000..6eac2b1
--- /dev/null
+++ b/test/elixir/test/partition_helpers.exs
@@ -0,0 +1,76 @@
+defmodule PartitionHelpers do
+  use ExUnit.Case
+
+  def create_partition_docs(db_name, pk1 \\ "foo", pk2 \\ "bar") do
+    docs =
+      for i <- 1..100 do
+        id =
+          if rem(i, 2) == 0 do
+            "#{pk1}:#{i}"
+          else
+            "#{pk2}:#{i}"
+          end
+
+        group =
+          if rem(i, 3) == 0 do
+            "one"
+          else
+            "two"
+          end
+
+        %{
+          :_id => id,
+          :value => i,
+          :some => "field",
+          :group => group
+        }
+      end
+
+    resp = Couch.post("/#{db_name}/_bulk_docs", body: %{:w => 3, :docs => docs})
+    assert resp.status_code == 201
+  end
+
+  def create_partition_ddoc(db_name, opts \\ %{}) do
+    map_fn = """
+      function(doc) {
+        if (doc.some) {
+          emit(doc.value, doc.some);
+        }
+      }
+    """
+
+    default_ddoc = %{
+      views: %{
+        some: %{
+          map: map_fn
+        }
+      }
+    }
+
+    ddoc = Enum.into(opts, default_ddoc)
+
+    resp = Couch.put("/#{db_name}/_design/mrtest", 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
+
+  def get_partitions(resp) do
+    %{:body => %{"rows" => rows}} = resp
+
+    Enum.map(rows, fn row ->
+      [partition, _] = String.split(row["id"], ":")
+      partition
+    end)
+  end
+
+  def assert_correct_partition(partitions, correct_partition) do
+    assert Enum.all?(partitions, fn partition ->
+             partition == correct_partition
+           end)
+  end
+end
diff --git a/test/elixir/test/partition_mango_test.exs b/test/elixir/test/partition_mango_test.exs
new file mode 100644
index 0000000..1471ddb
--- /dev/null
+++ b/test/elixir/test/partition_mango_test.exs
@@ -0,0 +1,591 @@
+defmodule PartitionMangoTest do
+  use CouchTestCase
+  import PartitionHelpers, except: [get_partitions: 1]
+
+  @moduledoc """
+  Test Partition functionality for mango
+  """
+  def create_index(db_name, fields \\ ["some"], opts \\ %{}) do
+    default_index = %{
+      index: %{
+        fields: fields
+      }
+    }
+
+    index = Enum.into(opts, default_index)
+    resp = Couch.post("/#{db_name}/_index", body: index)
+
+    assert resp.status_code == 200
+    assert resp.body["result"] == "created"
+  end
+
+  def get_partitions(resp) do
+    %{:body => %{"docs" => docs}} = resp
+
+    Enum.map(docs, fn doc ->
+      [partition, _] = String.split(doc["_id"], ":")
+      partition
+    end)
+  end
+
+  @tag :with_partitioned_db
+  test "query using _id and partition works", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name)
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            _id: %{
+              "$gt": "foo:"
+            }
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            _id: %{
+              "$lt": "foo:"
+            }
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "bar")
+  end
+
+  @tag :with_partitioned_db
+  test "query using _id works for global and local query", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name)
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            _id: %{
+              "$gt": 0
+            }
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            _id: %{
+              "$gt": 0
+            }
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "bar")
+  end
+
+  @tag :with_partitioned_db
+  test "query with partitioned:true using index and $eq", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name)
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_partition/bar/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "bar")
+  end
+
+  @tag :with_partitioned_db
+  test "partitioned query using _all_docs with $eq", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_partition/bar/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          },
+          limit: 20
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 20
+    assert_correct_partition(partitions, "bar")
+  end
+
+  @tag :with_db
+  test "non-partitioned query using _all_docs and $eq", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+
+    url = "/#{db_name}/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          },
+          skip: 40,
+          limit: 5
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert partitions == ["bar", "bar", "bar", "bar", "bar"]
+
+    url = "/#{db_name}/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          },
+          skip: 50,
+          limit: 5
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert partitions == ["foo", "foo", "foo", "foo", "foo"]
+  end
+
+  @tag :with_partitioned_db
+  test "partitioned query using index and range scan", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name, "foo", "bar42")
+    create_index(db_name, ["value"])
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_partition/bar42/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert_correct_partition(partitions, "bar42")
+  end
+
+  @tag :with_partitioned_db
+  test "partitioned query using _all_docs and range scan", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_partition/bar/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert_correct_partition(partitions, "bar")
+  end
+
+  @tag :with_partitioned_db
+  test "partitioned query using _all_docs", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name, "foo", "bar42")
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert_correct_partition(partitions, "foo")
+
+    url = "/#{db_name}/_partition/bar42/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert_correct_partition(partitions, "bar42")
+  end
+
+  @tag :with_partitioned_db
+  test "explain works with partitions", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name, ["some"])
+
+    url = "/#{db_name}/_partition/foo/_explain"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    %{:body => body} = resp
+
+    assert body["index"]["name"] == "_all_docs"
+    assert body["mrargs"]["partition"] == "foo"
+
+    url = "/#{db_name}/_partition/bar/_explain"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          }
+        }
+      )
+
+    %{:body => body} = resp
+
+    assert body["index"]["def"] == %{"fields" => [%{"some" => "asc"}]}
+    assert body["mrargs"]["partition"] == "bar"
+  end
+
+  @tag :with_db
+  test "explain works with non partitioned db", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name, ["some"])
+
+    url = "/#{db_name}/_explain"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          }
+        }
+      )
+
+    %{:body => body} = resp
+
+    assert body["index"]["name"] == "_all_docs"
+    assert body["mrargs"]["partition"] == :null
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            some: "field"
+          }
+        }
+      )
+
+    %{:body => body} = resp
+
+    assert body["index"]["def"] == %{"fields" => [%{"some" => "asc"}]}
+    assert body["mrargs"]["partition"] == :null
+  end
+
+  @tag :with_partitioned_db
+  test "partitioned query using bookmarks", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name, ["value"])
+
+    url = "/#{db_name}/_partition/foo/_find"
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          },
+          limit: 3
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 3
+    assert_correct_partition(partitions, "foo")
+
+    %{:body => %{"bookmark" => bookmark}} = resp
+
+    resp =
+      Couch.post(url,
+        body: %{
+          selector: %{
+            value: %{
+              "$gte": 6,
+              "$lt": 16
+            }
+          },
+          limit: 3,
+          bookmark: bookmark
+        }
+      )
+
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 2
+    assert_correct_partition(partitions, "foo")
+  end
+
+  @tag :with_partitioned_db
+  test "global query uses global index", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name, ["some"], %{partitioned: false})
+
+    url = "/#{db_name}/_explain"
+
+    selector = %{
+      selector: %{
+        some: "field"
+      },
+      limit: 100
+    }
+
+    resp = Couch.post(url, body: selector)
+    assert resp.status_code == 200
+    %{:body => body} = resp
+    assert body["index"]["def"] == %{"fields" => [%{"some" => "asc"}]}
+
+    url = "/#{db_name}/_find"
+    resp = Couch.post(url, body: selector)
+    assert resp.status_code == 200
+
+    partitions = get_partitions(resp)
+    assert length(partitions) == 100
+  end
+
+  @tag :with_partitioned_db
+  test "global query does not use partition index", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name, ["some"])
+
+    url = "/#{db_name}/_explain"
+
+    selector = %{
+      selector: %{
+        some: "field"
+      },
+      limit: 100
+    }
+
+    resp = Couch.post(url, body: selector)
+    %{:body => body} = resp
+    assert body["index"]["name"] == "_all_docs"
+
+    url = "/#{db_name}/_find"
+    resp = Couch.post(url, body: selector)
+
+    assert resp.status_code == 200
+
+    partitions = get_partitions(resp)
+    assert length(partitions) == 100
+  end
+
+  @tag :with_partitioned_db
+  test "partitioned query does not use global index", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_index(db_name, ["some"], %{partitioned: false})
+
+    url = "/#{db_name}/_partition/foo/_explain"
+
+    selector = %{
+      selector: %{
+        some: "field"
+      },
+      limit: 50
+    }
+
+    resp = Couch.post(url, body: selector)
+    assert resp.status_code == 200
+    %{:body => body} = resp
+    assert body["index"]["name"] == "_all_docs"
+
+    url = "/#{db_name}/_partition/foo/_find"
+    resp = Couch.post(url, body: selector)
+    assert resp.status_code == 200
+
+    partitions = get_partitions(resp)
+    assert length(partitions) == 50
+    assert_correct_partition(partitions, "foo")
+  end
+end
diff --git a/test/elixir/test/partition_size_test.exs b/test/elixir/test/partition_size_test.exs
new file mode 100644
index 0000000..289d1e1
--- /dev/null
+++ b/test/elixir/test/partition_size_test.exs
@@ -0,0 +1,265 @@
+defmodule PartitionSizeTest do
+  use CouchTestCase
+
+  @moduledoc """
+  Test Partition size functionality
+  """
+
+  setup do
+    db_name = random_db_name()
+    {:ok, _} = create_db(db_name, query: %{partitioned: true, q: 1})
+    on_exit(fn -> delete_db(db_name) end)
+
+    {:ok, [db_name: db_name]}
+  end
+
+  def get_db_info(dbname) do
+    resp = Couch.get("/#{dbname}")
+    assert resp.status_code == 200
+    %{:body => body} = resp
+    body
+  end
+
+  def get_partition_info(dbname, partition) do
+    resp = Couch.get("/#{dbname}/_partition/#{partition}")
+    assert resp.status_code == 200
+    %{:body => body} = resp
+    body
+  end
+
+  def mk_partition(i) do
+    i |> rem(10) |> Integer.to_string() |> String.pad_leading(3, "0")
+  end
+
+  def mk_docid(i) do
+    id = i |> Integer.to_string() |> String.pad_leading(4, "0")
+    "#{mk_partition(i)}:#{id}"
+  end
+
+  def mk_docs(db_name) do
+    docs =
+      for i <- 1..100 do
+        group = Integer.to_string(rem(i, 3))
+
+        %{
+          :_id => mk_docid(i),
+          :value => i,
+          :some => "field",
+          :group => group
+        }
+      end
+
+    body = %{:w => 3, :docs => docs}
+    resp = Couch.post("/#{db_name}/_bulk_docs", body: body)
+    assert resp.status_code == 201
+  end
+
+  def save_doc(db_name, doc) do
+    resp = Couch.post("/#{db_name}", query: [w: 3], body: doc)
+    assert resp.status_code == 201
+    %{:body => body} = resp
+    body["rev"]
+  end
+
+  test "get empty partition", context do
+    db_name = context[:db_name]
+    partition = "non_existent_partition"
+
+    info = get_partition_info(db_name, partition)
+
+    assert info["doc_count"] == 0
+    assert info["doc_del_count"] == 0
+    assert info["partition"] == partition
+    assert info["sizes"]["external"] == 0
+    assert info["sizes"]["active"] == 0
+  end
+
+  test "simple partition size", context do
+    db_name = context[:db_name]
+    save_doc(db_name, %{_id: "foo:bar", val: 42})
+
+    info = get_partition_info(db_name, "foo")
+    assert info["doc_count"] == 1
+    assert info["doc_del_count"] == 0
+    assert info["sizes"]["external"] > 0
+    assert info["sizes"]["active"] > 0
+  end
+
+  test "adding docs increases partition sizes", context do
+    db_name = context[:db_name]
+    save_doc(db_name, %{_id: "foo:bar", val: 42})
+    pre_info = get_partition_info(db_name, "foo")
+
+    save_doc(db_name, %{_id: "foo:baz", val: 24})
+    post_info = get_partition_info(db_name, "foo")
+
+    assert post_info["doc_count"] == 2
+    assert post_info["doc_del_count"] == 0
+    assert post_info["sizes"]["external"] > pre_info["sizes"]["external"]
+    assert post_info["sizes"]["active"] > pre_info["sizes"]["active"]
+  end
+
+  test "updating docs affects partition sizes", context do
+    db_name = context[:db_name]
+    rev1 = save_doc(db_name, %{_id: "foo:bar", val: ""})
+    info1 = get_partition_info(db_name, "foo")
+
+    rev2 =
+      save_doc(db_name, %{
+        _id: "foo:bar",
+        _rev: rev1,
+        val: "this is a very long string that is so super long its beyond long"
+      })
+
+    info2 = get_partition_info(db_name, "foo")
+
+    save_doc(db_name, %{
+      _id: "foo:bar",
+      _rev: rev2,
+      val: "this string is shorter"
+    })
+
+    info3 = get_partition_info(db_name, "foo")
+
+    assert info3["doc_count"] == 1
+    assert info3["doc_del_count"] == 0
+
+    assert info3["sizes"]["external"] > info1["sizes"]["external"]
+    assert info2["sizes"]["external"] > info3["sizes"]["external"]
+  end
+
+  test "deleting a doc affects partition sizes", context do
+    db_name = context[:db_name]
+    rev1 = save_doc(db_name, %{_id: "foo:bar", val: "some stuff here"})
+    info1 = get_partition_info(db_name, "foo")
+
+    save_doc(db_name, %{_id: "foo:bar", _rev: rev1, _deleted: true})
+    info2 = get_partition_info(db_name, "foo")
+
+    assert info1["doc_count"] == 1
+    assert info1["doc_del_count"] == 0
+
+    assert info2["doc_count"] == 0
+    assert info2["doc_del_count"] == 1
+
+    assert info2["sizes"]["external"] < info1["sizes"]["external"]
+  end
+
+  test "design docs do not affect partition sizes", context do
+    db_name = context[:db_name]
+    mk_docs(db_name)
+
+    pre_infos =
+      0..9
+      |> Enum.map(fn i ->
+        get_partition_info(db_name, mk_partition(i))
+      end)
+
+    0..5
+    |> Enum.map(fn i ->
+      base = i |> Integer.to_string() |> String.pad_leading(5, "0")
+      docid = "_design/#{base}"
+      save_doc(db_name, %{_id: docid, value: "some stuff here"})
+    end)
+
+    post_infos =
+      0..9
+      |> Enum.map(fn i ->
+        get_partition_info(db_name, mk_partition(i))
+      end)
+
+    assert post_infos == pre_infos
+  end
+
+  test "get all partition sizes", context do
+    db_name = context[:db_name]
+    mk_docs(db_name)
+
+    {esum, asum} =
+      0..9
+      |> Enum.reduce({0, 0}, fn i, {esize, asize} ->
+        partition = mk_partition(i)
+        info = get_partition_info(db_name, partition)
+        assert info["doc_count"] == 10
+        assert info["doc_del_count"] == 0
+        assert info["sizes"]["external"] > 0
+        assert info["sizes"]["active"] > 0
+        {esize + info["sizes"]["external"], asize + info["sizes"]["active"]}
+      end)
+
+    db_info = get_db_info(db_name)
+    assert db_info["sizes"]["external"] >= esum
+    assert db_info["sizes"]["active"] >= asum
+  end
+
+  test "get partition size with attachment", context do
+    db_name = context[:db_name]
+
+    doc = %{
+      _id: "foo:doc-with-attachment",
+      _attachments: %{
+        "foo.txt": %{
+          content_type: "text/plain",
+          data: Base.encode64("This is a text document to save")
+        }
+      }
+    }
+
+    save_doc(db_name, doc)
+
+    db_info = get_db_info(db_name)
+    foo_info = get_partition_info(db_name, "foo")
+
+    assert foo_info["doc_count"] == 1
+    assert foo_info["doc_del_count"] == 0
+    assert foo_info["sizes"]["active"] > 0
+    assert foo_info["sizes"]["external"] > 0
+
+    assert foo_info["sizes"]["active"] <= db_info["sizes"]["active"]
+    assert foo_info["sizes"]["external"] <= db_info["sizes"]["external"]
+  end
+
+  test "attachments don't affect other partitions", context do
+    db_name = context[:db_name]
+    mk_docs(db_name)
+
+    pre_infos =
+      0..9
+      |> Enum.map(fn i ->
+        get_partition_info(db_name, mk_partition(i))
+      end)
+
+    doc = %{
+      _id: "foo:doc-with-attachment",
+      _attachments: %{
+        "foo.txt": %{
+          content_type: "text/plain",
+          data: Base.encode64("This is a text document to save")
+        }
+      }
+    }
+
+    save_doc(db_name, doc)
+
+    att_info = get_partition_info(db_name, "foo")
+    assert att_info["doc_count"] == 1
+    assert att_info["sizes"]["external"] > 0
+
+    post_infos =
+      0..9
+      |> Enum.map(fn i ->
+        get_partition_info(db_name, mk_partition(i))
+      end)
+
+    assert post_infos == pre_infos
+
+    esize =
+      ([att_info] ++ post_infos)
+      |> Enum.reduce(0, fn info, acc ->
+        info["sizes"]["external"] + acc
+      end)
+
+    db_info = get_db_info(db_name)
+    assert esize == db_info["sizes"]["external"]
+  end
+end
diff --git a/test/elixir/test/partition_view_test.exs b/test/elixir/test/partition_view_test.exs
new file mode 100644
index 0000000..a255391
--- /dev/null
+++ b/test/elixir/test/partition_view_test.exs
@@ -0,0 +1,299 @@
+defmodule ViewPartitionTest do
+  use CouchTestCase
+  import PartitionHelpers
+
+  @moduledoc """
+  Test Partition functionality for views
+  """
+
+  setup_all do
+    db_name = random_db_name()
+    {:ok, _} = create_db(db_name, query: %{partitioned: true, q: 1})
+    on_exit(fn -> delete_db(db_name) end)
+
+    create_partition_docs(db_name)
+
+    map_fun1 = """
+      function(doc) {
+        if (doc.some) {
+          emit(doc.value, doc.some);
+        }
+      }
+    """
+
+    map_fun2 = """
+      function(doc) {
+        if (doc.group) {
+          emit([doc.some, doc.group], 1);
+        }
+      }
+    """
+
+    query = %{:w => 3}
+
+    body = %{
+      :docs => [
+        %{
+          _id: "_design/map",
+          views: %{some: %{map: map_fun1}}
+        },
+        %{
+          _id: "_design/map_some",
+          views: %{some: %{map: map_fun2}}
+        },
+        %{
+          _id: "_design/partitioned_true",
+          views: %{some: %{map: map_fun1}},
+          options: %{partitioned: true}
+        },
+        %{
+          _id: "_design/partitioned_false",
+          views: %{some: %{map: map_fun1}},
+          options: %{partitioned: false}
+        },
+        %{
+          _id: "_design/reduce",
+          views: %{some: %{map: map_fun2, reduce: "_count"}}
+        },
+        %{
+          _id: "_design/include_ddocs",
+          views: %{some: %{map: map_fun1}},
+          options: %{include_design: true}
+        }
+      ]
+    }
+
+    resp = Couch.post("/#{db_name}/_bulk_docs", query: query, body: body)
+    Enum.each(resp.body, &assert(&1["ok"]))
+
+    {:ok, [db_name: db_name]}
+  end
+
+  def get_reduce_result(resp) do
+    %{:body => %{"rows" => rows}} = resp
+    rows
+  end
+
+  test "query with partitioned:true returns partitioned fields", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/partitioned_true/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert Enum.dedup(partitions) == ["foo"]
+
+    url = "/#{db_name}/_partition/bar/_design/partitioned_true/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert Enum.dedup(partitions) == ["bar"]
+  end
+
+  test "default view query returns partitioned fields", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert Enum.dedup(partitions) == ["foo"]
+
+    url = "/#{db_name}/_partition/bar/_design/map/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert Enum.dedup(partitions) == ["bar"]
+  end
+
+  test "query will return zero results for wrong inputs", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url, query: %{start_key: "\"foo:12\""})
+    assert resp.status_code == 200
+    assert Map.get(resp, :body)["rows"] == []
+  end
+
+  test "partitioned ddoc cannot be used in global query", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_design/map/_view/some"
+    resp = Couch.get(url)
+    %{:body => %{"reason" => reason}} = resp
+    assert resp.status_code == 400
+    assert Regex.match?(~r/mandatory for queries to this view./, reason)
+  end
+
+  test "partitioned query cannot be used with global ddoc", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/partitioned_false/_view/some"
+    resp = Couch.get(url)
+    %{:body => %{"reason" => reason}} = resp
+    assert resp.status_code == 400
+    assert Regex.match?(~r/is not supported in this design doc/, reason)
+  end
+
+  test "view query returns all docs for global query", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_design/partitioned_false/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 100
+  end
+
+  test "partition query errors with incorrect partition supplied", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/_bar/_design/map/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 400
+
+    url = "/#{db_name}/_partition//_design/map/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 400
+  end
+
+  test "partitioned query works with startkey, endkey range", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url, query: %{start_key: 12, end_key: 20})
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert Enum.dedup(partitions) == ["foo"]
+  end
+
+  test "partitioned query works with keys", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.post(url, body: %{keys: [2, 4, 6]})
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 3
+    assert ids == ["foo:2", "foo:4", "foo:6"]
+  end
+
+  test "global query works with keys", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_design/partitioned_false/_view/some"
+    resp = Couch.post(url, body: %{keys: [2, 4, 6]})
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 3
+    assert ids == ["foo:2", "foo:4", "foo:6"]
+  end
+
+  test "partition query works with limit", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url, query: %{limit: 5})
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 5
+    assert Enum.dedup(partitions) == ["foo"]
+  end
+
+  test "partition query with descending", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url, query: %{descending: true, limit: 5})
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 5
+    assert ids == ["foo:100", "foo:98", "foo:96", "foo:94", "foo:92"]
+
+    resp = Couch.get(url, query: %{descending: false, limit: 5})
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 5
+    assert ids == ["foo:2", "foo:4", "foo:6", "foo:8", "foo:10"]
+  end
+
+  test "partition query with skip", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url, query: %{skip: 5, limit: 5})
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 5
+    assert ids == ["foo:12", "foo:14", "foo:16", "foo:18", "foo:20"]
+  end
+
+  test "partition query with key", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map/_view/some"
+    resp = Couch.get(url, query: %{key: 22})
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert length(ids) == 1
+    assert ids == ["foo:22"]
+  end
+
+  test "partition query with startkey_docid and endkey_docid", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/map_some/_view/some"
+
+    resp =
+      Couch.get(url,
+        query: %{
+          startkey: "[\"field\",\"one\"]",
+          endkey: "[\"field\",\"one\"]",
+          startkey_docid: "foo:12",
+          endkey_docid: "foo:30"
+        }
+      )
+
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert ids == ["foo:12", "foo:18", "foo:24", "foo:30"]
+  end
+
+  test "query with reduce works", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/reduce/_view/some"
+    resp = Couch.get(url, query: %{reduce: true, group_level: 1})
+    assert resp.status_code == 200
+    results = get_reduce_result(resp)
+    assert results == [%{"key" => ["field"], "value" => 50}]
+
+    resp = Couch.get(url, query: %{reduce: true, group_level: 2})
+    results = get_reduce_result(resp)
+
+    assert results == [
+             %{"key" => ["field", "one"], "value" => 16},
+             %{"key" => ["field", "two"], "value" => 34}
+           ]
+
+    resp = Couch.get(url, query: %{reduce: true, group: true})
+    results = get_reduce_result(resp)
+
+    assert results == [
+             %{"key" => ["field", "one"], "value" => 16},
+             %{"key" => ["field", "two"], "value" => 34}
+           ]
+  end
+
+  test "include_design works correctly", context do
+    db_name = context[:db_name]
+
+    url = "/#{db_name}/_partition/foo/_design/include_ddocs/_view/some"
+    resp = Couch.get(url)
+    assert resp.status_code == 200
+    partitions = get_partitions(resp)
+    assert length(partitions) == 50
+    assert Enum.dedup(partitions) == ["foo"]
+  end
+end
diff --git a/test/elixir/test/partition_view_update_test.exs b/test/elixir/test/partition_view_update_test.exs
new file mode 100644
index 0000000..1fc9839
--- /dev/null
+++ b/test/elixir/test/partition_view_update_test.exs
@@ -0,0 +1,73 @@
+defmodule PartitionViewUpdateTest do
+  use CouchTestCase
+  import PartitionHelpers
+
+  @moduledoc """
+  Test Partition view update functionality
+  """
+  @tag :with_partitioned_db
+  test "view updates properly remove old keys", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name, "foo", "bar")
+    create_partition_ddoc(db_name)
+
+    check_key = fn key, num_rows ->
+      url = "/#{db_name}/_partition/foo/_design/mrtest/_view/some"
+      resp = Couch.get(url, query: [key: key])
+      assert resp.status_code == 200
+      assert length(resp.body["rows"]) == num_rows
+    end
+
+    check_key.(2, 1)
+
+    resp = Couch.get("/#{db_name}/foo:2")
+    doc = Map.put(resp.body, "value", 4)
+    resp = Couch.put("/#{db_name}/foo:2", query: [w: 3], body: doc)
+    assert resp.status_code >= 201 and resp.status_code <= 202
+
+    check_key.(4, 2)
+    check_key.(2, 0)
+  end
+
+  @tag :with_partitioned_db
+  test "query with update=false works", context do
+    db_name = context[:db_name]
+    create_partition_docs(db_name)
+    create_partition_ddoc(db_name)
+
+    url = "/#{db_name}/_partition/foo/_design/mrtest/_view/some"
+
+    resp =
+      Couch.get(url,
+        query: %{
+          update: "true",
+          limit: 3
+        }
+      )
+
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert ids == ["foo:2", "foo:4", "foo:6"]
+
+    # Avoid race conditions by attempting to get a full response
+    # from every shard before we do our update:false test
+    for _ <- 1..12 do
+      resp = Couch.get(url)
+      assert resp.status_code == 200
+    end
+
+    Couch.put("/#{db_name}/foo:1", body: %{some: "field"})
+
+    resp =
+      Couch.get(url,
+        query: %{
+          update: "false",
+          limit: 3
+        }
+      )
+
+    assert resp.status_code == 200
+    ids = get_ids(resp)
+    assert ids == ["foo:2", "foo:4", "foo:6"]
+  end
+end
diff --git a/test/elixir/test/test_helper.exs b/test/elixir/test/test_helper.exs
index 33041fd..d6843eb 100644
--- a/test/elixir/test/test_helper.exs
+++ b/test/elixir/test/test_helper.exs
@@ -1,2 +1,3 @@
 ExUnit.configure(exclude: [pending: true])
 ExUnit.start()
+Code.require_file("partition_helpers.exs", __DIR__)