You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by gl...@apache.org on 2020/02/12 11:26:01 UTC

[couchdb-nano] 13/15: partitioned functions and tests

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

glynnbird pushed a commit to branch jest
in repository https://gitbox.apache.org/repos/asf/couchdb-nano.git

commit 62e318f0d4feffafc132f8d9ea4f226db4e0c744
Author: Glynn Bird <gl...@gmail.com>
AuthorDate: Tue Feb 11 10:36:23 2020 +0000

    partitioned functions and tests
---
 README.md                             | 206 ++++++++++++++++++++++++++++++++++
 lib/nano.d.ts                         |  54 +++++++++
 lib/nano.js                           | 129 ++++++++++++++++++++-
 test/document.get.test.js             |  14 +++
 test/partition.find.test.js           |  87 ++++++++++++++
 test/partition.findAsStream.test.js   |  60 ++++++++++
 test/partition.info.test.js           |  88 +++++++++++++++
 test/partition.list.test.js           | 150 +++++++++++++++++++++++++
 test/partition.listAsStream.test.js   | 112 ++++++++++++++++++
 test/partition.search.test.js         |  78 +++++++++++++
 test/partition.searchAsStream.test.js |  51 +++++++++
 test/partition.view.test.js           |  98 ++++++++++++++++
 test/partition.viewAsStream.test.js   |  54 +++++++++
 13 files changed, 1180 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index f98afa1..94a2118 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,16 @@ See [Migration Guide for switching from Nano 6.x to 7.x](migration_6_to_7.md).
   - [db.fetch(docnames, [params], [callback])](#dbfetchdocnames-params-callback)
   - [db.fetchRevs(docnames, [params], [callback])](#dbfetchrevsdocnames-params-callback)
   - [db.createIndex(indexDef, [callback])](#dbcreateindexindexdef-callback)
+- [Partitioned database functions][#partition-functions]
+  - [db.partitionInfo(partitionKey, [callback])](#dbpartitioninfopartitionkey-callback))
+  - [db.partitionedList(partitionKey, [params], [callback])](#dbpartitionedlistpartitionkey-params-callback)
+  - [db.partitionedListAsStream(partitionKey, [params])](#dbpartitionedlistasstreampartitionkey-params)
+  - [db.partitionedFind(partitionKey, query, [callback])](#dbpartitionedfindpartitionkey-query-params)
+  - [db.partitionedFindAsStream(partitionKey, query)](#dbpartitionedfindasstreampartitionkey-query)
+  - [db.partitionedSearch(partitionKey, designName, searchName, params, [callback])](#dbpartitionedsearchpartitioney-designname-searchname-params-callback)
+  - [db.partitionedSearchAsStream(partitionKey, designName, searchName, params)](#dbpartitionedsearchasstreampartitionkey-designName-searchName-params)
+  - [db.partitionedView(partitionKey, designName, viewName, [params], [callback])](#dbpartitionediewpartitionkey-designname-viewname-params-callback)
+  - [db.partitionedViewAsStream(partitionKey, designName, viewName, [params])](#dbpartitionediewasstreampartitionkey-designname-viewname-params)
 - [Multipart functions](#multipart-functions)
   - [db.multipart.insert(doc, attachments, [params], [callback])](#dbmultipartinsertdoc-attachments-params-callback)
   - [db.multipart.get(docname, [params], [callback])](#dbmultipartgetdocname-params-callback)
@@ -702,6 +712,202 @@ alice.createIndex(indexDef).then((result) => {
 });
 ```
 
+## Partition Functions
+
+Functions related to [partitioned databses](https://docs.couchdb.org/en/latest/partitioned-dbs/index.html).
+
+Create a partitioned database by passing `{ partitioned: true }` to `db.create`:
+
+```js
+await nano.db.create('my-partitioned-db', { partitioned: true })
+```
+
+The database can be used as normal:
+
+```js
+const db = nano.db.use('my-partitioned-db')
+```
+
+but documents must have a two-part `_id` made up of `<partition key>:<document id>`. They are insert with `db.insert` as normal:
+
+```js
+const doc = { _id: 'canidae:dog', name: 'Dog', latin: 'Canis lupus familiaris' }
+await db.insert(doc)
+```
+
+Documents can be retrieved by their `_id` using `db.get`:
+
+```js
+const doc = db.get('canidae:dog')
+```
+
+Mango indexes can be created to operate on a per-partition index by supplying `partitioned: true` on creation:
+
+```js
+const i = {
+  ddoc: 'partitioned-query',
+  index: { fields: ['name'] },
+  name: 'name-index',
+  partitioned: true,
+  type: 'json'
+}
+ 
+// instruct CouchDB to create the index
+await db.index(i)
+```
+
+Search indexes can be created by writing a design document with `opts.partitioned = true`:
+
+```js
+// the search definition
+const func = function(doc) {
+  index('name', doc.name)
+  index('latin', doc.latin)
+}
+ 
+// the design document containing the search definition function
+const ddoc = {
+  _id: '_design/search-ddoc',
+  indexes: {
+    search-index: {
+      index: func.toString()
+    }
+  },
+  options: {
+    partitioned: true
+  }
+}
+ 
+await db.insert(ddoc)
+```
+
+MapReduce views can be created by writing a design document with `opts.partitioned = true`:
+
+```js
+const func = function(doc) {
+  emit(doc.family, doc.weight)
+}
+ 
+// Design Document
+const ddoc = {
+  _id: '_design/view-ddoc',
+  views: {
+    family-weight: {
+      map: func.toString(),
+      reduce: '_sum'
+    }
+  },
+  options: {
+    partitioned: true
+  }
+}
+ 
+// create design document
+await db.insert(ddoc)
+```
+
+### db.partitionInfo(partitionKey, [callback])
+
+Fetch the stats of a single partition:
+
+```js
+const stats = await alice.partitionInfo('canidae')
+```
+
+### db.partitionedList(partitionKey, [params], [callback])
+
+Fetch documents from a database partition:
+
+```js
+// fetch document id/revs from a partition
+const docs = await alice.partitionedList('canidae')
+
+// add document bodies but limit size of response
+const docs = await alice.partitionedList('canidae', { include_docs: true, limit: 5 })
+```
+
+### db.partitionedListAsStream(partitionKey, [params])
+
+Fetch documents from a partition as a stream:
+
+```js
+// fetch document id/revs from a partition
+nano.db.partitionedListAsStream('canidae').pipe(process.stdout)
+
+// add document bodies but limit size of response
+nano.db.partitionedListAsStream('canidae', { include_docs: true, limit: 5 }).pipe(process.stdout)
+```
+
+### db.partitionedFind(partitionKey, query, [params])
+
+Query documents from a partition by supplying a Mango selector:
+
+```js
+// find document whose name is 'wolf' in the 'canidae' partition
+await db.partitionedFind('canidae', { 'selector' : { 'name': 'Wolf' }})
+```
+
+### db.partitionedFindAsStream(partitionKey, query)
+
+Query documents from a partition by supplying a Mango selector as a stream:
+
+```js
+// find document whose name is 'wolf' in the 'canidae' partition
+db.partitionedFindAsStream('canidae', { 'selector' : { 'name': 'Wolf' }}).pipe(process.stdout)
+```
+
+### db.partitionedSearch(partitionKey, designName, searchName, params, [callback])
+
+Search documents from a partition by supplying a Lucene query:
+
+```js
+const params = {
+  q: 'name:\'Wolf\''
+}
+await db.partitionedSearch('canidae', 'search-ddoc', 'search-index', params)
+// { total_rows: ... , bookmark: ..., rows: [ ...] }
+```
+
+### db.partitionedSearchAsStream(partitionKey, designName, searchName, params)
+
+Search documents from a partition by supplying a Lucene query as a stream:
+
+```js
+const params = {
+  q: 'name:\'Wolf\''
+}
+db.partitionedSearchAsStream('canidae', 'search-ddoc', 'search-index', params).pipe(process.stdout)
+// { total_rows: ... , bookmark: ..., rows: [ ...] }
+```
+
+### db.partitionedView(partitionKey, designName, viewName, params, [callback])
+
+Fetch documents from a MapReduce view from a partition:
+
+```js
+const params = {
+  startkey: 'a',
+  endkey: 'b',
+  limit: 1
+}
+await db.partitionedView('canidae', 'view-ddoc', 'view-name', params)
+// { rows: [ { key: ... , value: [Object] } ] }
+```
+
+### db.partitionedViewAsStream(partitionKey, designName, viewName, params)
+
+Fetch documents from a MapReduce view from a partition as a stream:
+
+```js
+const params = {
+  startkey: 'a',
+  endkey: 'b',
+  limit: 1
+}
+db.partitionedView('canidae', 'view-ddoc', 'view-name', params).pipe(process.stdout)
+// { rows: [ { key: ... , value: [Object] } ] }
+```
+
 ## Multipart functions
 
 ### db.multipart.insert(doc, attachments, params, [callback])
diff --git a/lib/nano.d.ts b/lib/nano.d.ts
index ca5a29f..d91917c 100644
--- a/lib/nano.d.ts
+++ b/lib/nano.d.ts
@@ -302,6 +302,38 @@ declare namespace nano {
     // http://docs.couchdb.org/en/latest/api/database/find.html#db-find
     find(query: MangoQuery, callback?: Callback<MangoResponse<D>>): Promise <MangoResponse<D>>;
     server: ServerScope;
+    //https://docs.couchdb.org/en/latest/partitioned-dbs/index.html
+    partitionInfo(partitionKey: string, callback?: Callback<PartitionInfoResponse<D>>): Promise <PartitionInfoResponse>;
+    partitionedList(partitionKey: string, params?: DocumentListParams, callback?: Callback<DocumentListResponse<D>>): Promise<DocumentListResponse<D>>;
+    partitionedListAsStream(partitionKey: string, params?: DocumentListParams): Request;
+    partitionedFind(partitionKey: string, query: MangoQuery, callback?: Callback<MangoResponse<D>>): Promise <MangoResponse<D>>;
+    partitionedFindAsStream(partitionKey: string, query: MangoQuery): Request;
+    partitionedViewpartitionedSearch<V>(
+      partitionKey: string,
+      designname: string,
+      searchname: string,
+      params: DocumentSearchParams,
+      callback?: Callback<DocumentSearchResponse<V>>
+    ): Promise<DocumentSearchResponse<V>>;
+    partitionedSearchAsStream(
+      partitionKey: string,
+      designname: string,
+      searchname: string,
+      params: DocumentSearchParams
+    ): Request;
+    partitionedView<V>(
+      partitionKey: string,
+      designname: string,
+      viewname: string,
+      params: DocumentViewParams,
+      callback?: Callback<DocumentViewResponse<V,D>>
+    ): Promise<DocumentViewResponse<V,D>>;
+    partitionedViewAsStream<V>(
+      partitionKey: string,
+      designname: string,
+      viewname: string,
+      params: DocumentViewParams
+    ): Request;
   }
 
   interface AttachmentData {
@@ -1025,6 +1057,28 @@ declare namespace nano {
 
   }
 
+
+  // https://docs.couchdb.org/en/latest/partitioned-dbs/index.html
+  interface PartitionInfoResponse {
+    // Database name
+    db_name:  string;
+
+    // Partition sizes
+    sizes: {
+      active: integer;
+      external: integer;
+    }
+
+    // Partition name
+    partition: string;
+
+    // Document count
+    doc_count: number;
+
+    // Deleted document count
+    doc_del_count: number;
+  }
+
   // https://console.bluemix.net/docs/services/Cloudant/api/search.html#queries
   interface DocumentSearchParams {
     // A bookmark that was received from a previous search. Used for pagination.
diff --git a/lib/nano.js b/lib/nano.js
index c1bcc23..b4cc0d0 100644
--- a/lib/nano.js
+++ b/lib/nano.js
@@ -1076,6 +1076,124 @@ module.exports = exports = function dbScope (cfg) {
       }, callback)
     }
 
+    function partitionInfo (partitionKey, callback) {
+      if (!partitionKey) {
+        const e = INVALID_PARAMETERS_ERROR
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+      return relax({
+        db: dbName,
+        path: '_partition/' + partitionKey
+      }, callback)
+    }
+
+    function partitionedList (partitionKey, qs0, callback0) {
+      const { opts, callback } = getCallback(qs0, callback0)
+      if (!partitionKey) {
+        const e = INVALID_PARAMETERS_ERROR
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+      return relax({
+        db: dbName,
+        path: '_partition/' + partitionKey + '/_all_docs',
+        qs: opts
+      }, callback)
+    }
+
+    function partitionedListAsStream (partitionKey, qs) {
+      return relax({
+        db: dbName,
+        path: '_partition/' + partitionKey + '/_all_docs',
+        qs: qs,
+        stream: true
+      })
+    }
+
+    function partitionedFind (partition, query, callback) {
+      if (!partition || !query || typeof query !== 'object') {
+        const e = INVALID_PARAMETERS_ERROR
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+      return relax({
+        db: dbName,
+        path: '_partition/' + partition + '/_find',
+        method: 'POST',
+        body: query
+      }, callback)
+    }
+
+    function partitionedFindAsStream (partition, query) {
+      return relax({
+        db: dbName,
+        path: '_partition/' + partition + '/_find',
+        method: 'POST',
+        body: query,
+        stream: true
+      })
+    }
+
+    function partitionedSearch (partition, ddoc, searchName, opts, callback) {
+      if (!partition || !ddoc || !searchName || !opts || typeof opts !== 'object') {
+        const e = INVALID_PARAMETERS_ERROR
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+      return relax({
+        db: dbName,
+        path: '_partition/' + partition + '/_design/' + ddoc + '/_search/' + searchName,
+        qs: opts
+      }, callback)
+    }
+
+    function partitionedSearchAsStream (partition, ddoc, searchName, opts) {
+      return relax({
+        db: dbName,
+        path: '_partition/' + partition + '/_design/' + ddoc + '/_search/' + searchName,
+        qs: opts,
+        stream: true
+      })
+    }
+
+    function partitionedView (partition, ddoc, viewName, opts, callback) {
+      if (!partition || !ddoc || !viewName) {
+        const e = INVALID_PARAMETERS_ERROR
+        if (callback) {
+          return callback(e, null)
+        } else {
+          return Promise.reject(e)
+        }
+      }
+      return relax({
+        db: dbName,
+        path: '_partition/' + partition + '/_design/' + ddoc + '/_view/' + viewName,
+        qs: opts
+      }, callback)
+    }
+
+    function partitionedViewAsStream (partition, ddoc, viewName, opts) {
+      return relax({
+        db: dbName,
+        path: '_partition/' + partition + '/_design/' + ddoc + '/_view/' + viewName,
+        qs: opts,
+        stream: true
+      })
+    }
+
     // db level exports
     docScope = {
       info: function (cb) {
@@ -1143,7 +1261,16 @@ module.exports = exports = function dbScope (cfg) {
         query: function (id, opts, cb) {
           return queryReplication(id, opts, cb)
         }
-      }
+      },
+      partitionInfo: partitionInfo,
+      partitionedList: partitionedList,
+      partitionedListAsStream: partitionedListAsStream,
+      partitionedFind: partitionedFind,
+      partitionedFindAsStream: partitionedFindAsStream,
+      partitionedSearch: partitionedSearch,
+      partitionedSearchAsStream: partitionedSearchAsStream,
+      partitionedView: partitionedView,
+      partitionedViewAsStream: partitionedViewAsStream
     }
 
     docScope.view.compact = function (ddoc, cb) {
diff --git a/test/document.get.test.js b/test/document.get.test.js
index 109b7cb..4b45b5a 100644
--- a/test/document.get.test.js
+++ b/test/document.get.test.js
@@ -33,6 +33,20 @@ test('should be able to get a document - GET /db/id - db.get', async () => {
   expect(scope.isDone()).toBe(true)
 })
 
+test('should be able to get a document from a partition - GET /db/pkey:id - db.get', async () => {
+  // mocks
+  const response = { _id: 'partkey:id', rev: '1-123', a: 1, b: 'two', c: true }
+  const scope = nock(COUCH_URL)
+    .get('/db/partkey%3Aid')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.get('partkey:id')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
 test('should be able to get a document with options - GET /db/id?conflicts=true - db.get', async () => {
   // mocks
   const response = { _id: 'id', rev: '1-123', a: 1, b: 'two', c: true }
diff --git a/test/partition.find.test.js b/test/partition.find.test.js
new file mode 100644
index 0000000..325758a
--- /dev/null
+++ b/test/partition.find.test.js
@@ -0,0 +1,87 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should be able to query a partitioned index - POST /db/_partition/partition/_find - db.partitionedFind', async () => {
+  // mocks
+  const query = {
+    selector: {
+      $and: {
+        date: {
+          $gt: '2018'
+        },
+        name: 'Susan'
+      }
+    },
+    fields: ['name', 'date', 'orderid']
+  }
+  const response = {
+    docs: [
+      { name: 'Susan', date: '2019-01-02', orderid: '4411' },
+      { name: 'Susan', date: '2019-01-03', orderid: '8523' }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_partition/partition/_find', query)
+    .reply(200, response)
+
+  // test POST /db/_find
+  const db = nano.db.use('db')
+  const p = await db.partitionedFind('partition', query)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should handle 404 - POST /db/_partition/partition/_find - db.partitionedFind', async () => {
+  // mocks
+  const query = {
+    selector: {
+      name: 'Susan'
+    }
+  }
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_partition/partition/_find', query)
+    .reply(404, response)
+
+  // test POST /db/_find
+  const db = nano.db.use('db')
+  await expect(db.partitionedFind('partition', query)).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing query - db.partitionedFind', async () => {
+  const db = nano.db.use('db')
+  await expect(db.partitionedFind()).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedFind('partition', 'susan')).rejects.toThrow('Invalid parameters')
+})
+
+test('should detect missing query (callback) - db.partitionedFind', async () => {
+  const db = nano.db.use('db')
+  return new Promise((resolve, reject) => {
+    db.partitionedFind(undefined, '', (err, data) => {
+      expect(err).not.toBeNull()
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.findAsStream.test.js b/test/partition.findAsStream.test.js
new file mode 100644
index 0000000..340eadf
--- /dev/null
+++ b/test/partition.findAsStream.test.js
@@ -0,0 +1,60 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should get a queried streamed list of documents from a partition- POST /db/_partition/partition/_find - db.partitionedFindAsStream', async () => {
+  // mocks
+  const query = {
+    selector: {
+      $and: {
+        date: {
+          $gt: '2018'
+        },
+        name: 'Susan'
+      }
+    },
+    fields: ['name', 'date', 'orderid']
+  }
+  const response = {
+    docs: [
+      { name: 'Susan', date: '2019-01-02', orderid: '4411' },
+      { name: 'Susan', date: '2019-01-03', orderid: '8523' }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .post('/db/_partition/partition/_find')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.partitionedFindAsStream('partition', query)
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.info.test.js b/test/partition.info.test.js
new file mode 100644
index 0000000..52c6670
--- /dev/null
+++ b/test/partition.info.test.js
@@ -0,0 +1,88 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const db = nano.db.use('db')
+const nock = require('nock')
+const response = {
+  db_name: 'db',
+  sizes: {
+    active: 12955,
+    external: 15009
+  },
+  partition: 'partition',
+  doc_count: 28,
+  doc_del_count: 0
+}
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should be able to fetch partition info info - GET /db/_partition/partition - db.partitionInfo', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition')
+    .reply(200, response)
+
+  // test GET /db
+  const p = await db.partitionInfo('partition')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to fetch partition info info (callback) - GET /db/_partition/partition - db.partitionInfo', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition')
+    .reply(200, response)
+
+  // test GET /db
+  return new Promise((resolve, reject) => {
+    db.partitionInfo('partition', (err, data) => {
+      expect(err).toBeNull()
+      expect(data).toStrictEqual(response)
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('should handle missing database - PUT /db - nano.db.create', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition')
+    .reply(404, {
+      error: 'not_found',
+      reason: 'Database does not exist.'
+    })
+
+  // test GET /db
+  await expect(db.partitionInfo('partition')).rejects.toThrow('Database does not exist')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should not attempt info fetch with missing parameters - nano.db.get', async () => {
+  await expect(db.partitionInfo()).rejects.toThrowError('Invalid parameters')
+  await expect(db.partitionInfo('')).rejects.toThrowError('Invalid parameters')
+})
+
+test('should detect missing parameters (callback) - nano.db.get', async () => {
+  return new Promise((resolve, reject) => {
+    db.partitionInfo(undefined, (err, data) => {
+      expect(err).not.toBeNull()
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.list.test.js b/test/partition.list.test.js
new file mode 100644
index 0000000..c1b0e04
--- /dev/null
+++ b/test/partition.list.test.js
@@ -0,0 +1,150 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const db = nano.db.use('db')
+const nock = require('nock')
+const response = {
+  total_rows: 1215,
+  offset: 0,
+  rows: [
+    {
+      id: 'partition:00003it00tDmkP2Hdlkz1sMOyA12WmDj',
+      key: 'partition:00003it00tDmkP2Hdlkz1sMOyA12WmDj',
+      value: {
+        rev: '1-7feb3dec79cbff2506762ac7c8550c45'
+      }
+    },
+    {
+      id: 'partition:0000tvHi1qDnsy2Plek31OG9pw0cG8VG',
+      key: 'partition:0000tvHi1qDnsy2Plek31OG9pw0cG8VG',
+      value: {
+        rev: '1-d0c62e02a18c5b714ed94277c3852cf4'
+      }
+    },
+    {
+      id: 'partition:0000vEYK2zb89n1QMdnr1MQ5Ax0wMaUa',
+      key: 'partition:0000vEYK2zb89n1QMdnr1MQ5Ax0wMaUa',
+      value: {
+        rev: '1-42a99d13a33e46b1f37f4f937d167458'
+      }
+    },
+    {
+      id: 'partition:0003RAaK16cHJ03fOZYJ3zti9g22ppGr',
+      key: 'partition:0003RAaK16cHJ03fOZYJ3zti9g22ppGr',
+      value: {
+        rev: '1-11929970736f90c3955fda281847bf58'
+      }
+    },
+    {
+      id: 'partition:0008Cu6D1LcJm142gadC13KRDD1LNkYw',
+      key: 'partition:0008Cu6D1LcJm142gadC13KRDD1LNkYw',
+      value: {
+        rev: '1-325d1f25f719304b89c64862d2c27832'
+      }
+    }
+  ]
+}
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should be list documents form a partition - GET /db/_partition/_all_docs - db.partitionedList', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_all_docs')
+    .reply(200, response)
+
+  // test GET /db
+  const p = await db.partitionedList('partition')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be list documents form a partition with opts - GET /db/_partition/_all_docs - db.partitionedList', async () => {
+  // mocks
+  const optsResponse = {
+    total_rows: 1215,
+    offset: 0,
+    rows: [
+      {
+        id: 'partition:00003it00tDmkP2Hdlkz1sMOyA12WmDj',
+        key: 'partition:00003it00tDmkP2Hdlkz1sMOyA12WmDj',
+        value: {
+          rev: '1-7feb3dec79cbff2506762ac7c8550c45'
+        },
+        doc: {
+          _id: 'partition:00003it00tDmkP2Hdlkz1sMOyA12WmDj',
+          _rev: '1-7feb3dec79cbff2506762ac7c8550c45',
+          a: 1,
+          b: 2
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_all_docs?limit=1&include_docs=true')
+    .reply(200, optsResponse)
+
+  // test GET /db
+  const p = await db.partitionedList('partition', { limit: 1, include_docs: true })
+  expect(p).toStrictEqual(optsResponse)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to list partition docs (callback) - GET /db/_partition/_all_docs - db.partitionedList', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_all_docs')
+    .reply(200, response)
+
+  // test GET /db
+  return new Promise((resolve, reject) => {
+    db.partitionedList('partition', (err, data) => {
+      expect(err).toBeNull()
+      expect(data).toStrictEqual(response)
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('should handle missing database - GET /db/_partition/_all_docs - db.partitionedList', async () => {
+  // mocks
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_all_docs')
+    .reply(404, {
+      error: 'not_found',
+      reason: 'Database does not exist.'
+    })
+
+  // test GET /db
+  await expect(db.partitionedList('partition')).rejects.toThrow('Database does not exist')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should not attempt info fetch with missing parameters - db.partitionedList', async () => {
+  await expect(db.partitionedList()).rejects.toThrowError('Invalid parameters')
+  await expect(db.partitionedList('')).rejects.toThrowError('Invalid parameters')
+})
+
+test('should detect missing parameters (callback) - db.partitionedList', async () => {
+  return new Promise((resolve, reject) => {
+    db.partitionedList(undefined, (err, data) => {
+      expect(err).not.toBeNull()
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.listAsStream.test.js b/test/partition.listAsStream.test.js
new file mode 100644
index 0000000..f360cb2
--- /dev/null
+++ b/test/partition.listAsStream.test.js
@@ -0,0 +1,112 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should get a streamed list of documents from a partition- GET /db/_partition/partition/_all_docs - db.partitionedListAsStream', async () => {
+  // mocks
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: 'partition:1000501',
+        key: 'partition:1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        }
+      },
+      {
+        id: 'partition:1000543',
+        key: 'partition:1000543',
+        value: {
+          rev: '1-3256046064953e2f0fdb376211fe78ab'
+        }
+      },
+      {
+        id: 'partition:100077',
+        key: 'partition:100077',
+        value: {
+          rev: '1-101bff1251d4bd75beb6d3c232d05a5c'
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_all_docs')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.partitionedListAsStream('partition')
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
+
+test('should get a streamed list of documents from a partition with opts- GET /db/_all_docs - db.partitionedListAsStream', async () => {
+  // mocks
+  const response = {
+    total_rows: 23516,
+    offset: 0,
+    rows: [
+      {
+        id: 'partition:1000501',
+        key: 'partition:1000501',
+        value: {
+          rev: '2-46dcf6bf2f8d428504f5290e591aa182'
+        },
+        doc: {
+          _id: 'partition:1000501',
+          _rev: '2-46dcf6bf2f8d428504f5290e591aa182',
+          a: 1,
+          b: 2
+        }
+      }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_all_docs?limit=1&include_docs=true')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.partitionedListAsStream('partition', { limit: 1, include_docs: true })
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.search.test.js b/test/partition.search.test.js
new file mode 100644
index 0000000..9f470fb
--- /dev/null
+++ b/test/partition.search.test.js
@@ -0,0 +1,78 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should be able to access a partitioned search index - POST /db/_partition/partition/_design/ddoc/_search/searchname - db.partitionedSearch', async () => {
+  // mocks
+  const response = {
+    total_rows: 100000,
+    bookmark: 'g123',
+    rows: [
+      { a: 1, b: 2 }
+    ]
+  }
+  const params = { q: '*:*' }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_search/searchname?q=*:*')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.partitionedSearch('partition', 'ddoc', 'searchname', params)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 404 - db.partitionedSearch', async () => {
+  // mocks
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const params = { q: '*:*' }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_search/searchname?q=*:*')
+    .reply(404, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  await expect(db.partitionedSearch('partition', 'ddoc', 'searchname', params)).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing parameters - db.partitionedSearch', async () => {
+  const db = nano.db.use('db')
+  await expect(db.partitionedSearch()).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedSearch('partition', 'susan')).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedSearch('partition', 'susan', '')).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedSearch('partition', '', 'susan')).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedSearch('partition', 'susan', '', undefined)).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedSearch('partition', '', 'susan')).rejects.toThrow('Invalid parameters')
+})
+
+test('should detect missing parameters (callback) - db.partitionedSearch', async () => {
+  const db = nano.db.use('db')
+  return new Promise((resolve, reject) => {
+    db.partitionedSearch('', '', '', '', (err, data) => {
+      expect(err).not.toBeNull()
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.searchAsStream.test.js b/test/partition.searchAsStream.test.js
new file mode 100644
index 0000000..73faabd
--- /dev/null
+++ b/test/partition.searchAsStream.test.js
@@ -0,0 +1,51 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should get a searched streamed list of documents from a partition- GET /db/_partition/partition/_design/ddoc/_search/searchname - db.partitionedSearchAsStream', async () => {
+  // mocks
+  const response = {
+    total_rows: 100000,
+    bookmark: 'g123',
+    rows: [
+      { a: 1, b: 2 }
+    ]
+  }
+  const params = { q: '*:*' }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_search/searchname?q=*:*')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.partitionedSearchAsStream('partition', 'ddoc', 'searchname', params)
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.view.test.js b/test/partition.view.test.js
new file mode 100644
index 0000000..7338fcb
--- /dev/null
+++ b/test/partition.view.test.js
@@ -0,0 +1,98 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should be able to access a partitioned view index - GET /db/_partition/partition/_design/ddoc/_view/viewname - db.partitionedView', async () => {
+  // mocks
+  const response = {
+    rows: [
+      { key: null, value: 23515 }
+    ]
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_view/viewname')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.partitionedView('partition', 'ddoc', 'viewname')
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to access a partitioned view index with opts - GET /db/_partition/partition/_design/ddoc/_view/viewname - db.partitionedView', async () => {
+  // mocks
+  const response = {
+    rows: [
+      { key: 'a', value: null }
+    ]
+  }
+  const params = {
+    reduce: false,
+    startkey: 'a',
+    endkey: 'b',
+    limit: 1
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_view/viewname?reduce=false&startkey=%22a%22&endkey=%22b%22&limit=1')
+    .reply(200, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  const p = await db.partitionedView('partition', 'ddoc', 'viewname', params)
+  expect(p).toStrictEqual(response)
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should be able to handle 404 - db.partitionedView', async () => {
+  // mocks
+  const response = {
+    error: 'not_found',
+    reason: 'missing'
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_view/viewname')
+    .reply(404, response)
+
+  // test GET /db
+  const db = nano.db.use('db')
+  await expect(db.partitionedView('partition', 'ddoc', 'viewname')).rejects.toThrow('missing')
+  expect(scope.isDone()).toBe(true)
+})
+
+test('should detect missing parameters - db.partitionedView', async () => {
+  const db = nano.db.use('db')
+  await expect(db.partitionedView()).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedView('partition', 'susan')).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedView('partition', 'susan', '')).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedView('partition', '', 'susan')).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedView('partition', 'susan', '', undefined)).rejects.toThrow('Invalid parameters')
+  await expect(db.partitionedView('partition', '', 'susan')).rejects.toThrow('Invalid parameters')
+})
+
+test('should detect missing parameters (callback) - db.partitionedView', async () => {
+  const db = nano.db.use('db')
+  return new Promise((resolve, reject) => {
+    db.partitionedView('', '', '', '', (err, data) => {
+      expect(err).not.toBeNull()
+      resolve()
+    })
+  })
+})
diff --git a/test/partition.viewAsStream.test.js b/test/partition.viewAsStream.test.js
new file mode 100644
index 0000000..24a899f
--- /dev/null
+++ b/test/partition.viewAsStream.test.js
@@ -0,0 +1,54 @@
+// 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.
+
+const Nano = require('..')
+const COUCH_URL = 'http://localhost:5984'
+const nano = Nano(COUCH_URL)
+const nock = require('nock')
+
+afterEach(() => {
+  nock.cleanAll()
+})
+
+test('should get a streamed list of documents from a view from  partition- GET /db/_partition/partition/_design/ddoc/_view/viewname - db.partitionedViewAsStream', async () => {
+  // mocks
+  const response = {
+    rows: [
+      { key: 'a', value: null }
+    ]
+  }
+  const params = {
+    reduce: false,
+    startkey: 'a',
+    endkey: 'b',
+    limit: 1
+  }
+  const scope = nock(COUCH_URL)
+    .get('/db/_partition/partition/_design/ddoc/_view/viewname?reduce=false&startkey=%22a%22&endkey=%22b%22&limit=1')
+    .reply(200, response)
+
+  return new Promise((resolve, reject) => {
+    // test GET /db/_all_docs
+    const db = nano.db.use('db')
+    const s = db.partitionedViewAsStream('partition', 'ddoc', 'viewname', params)
+    expect(typeof s).toBe('object')
+    let buffer = ''
+    s.on('data', (chunk) => {
+      buffer += chunk.toString()
+    })
+    s.on('end', () => {
+      expect(buffer).toBe(JSON.stringify(response))
+      expect(scope.isDone()).toBe(true)
+      resolve()
+    })
+  })
+})