Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit d400375

Browse files
committed
Add partitioned database support.
1 parent 320329e commit d400375

File tree

4 files changed

+394
-2
lines changed

4 files changed

+394
-2
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# UNRELEASED
2+
- [NEW] Added partitioned database support.
3+
14
# 4.0.0 (2019-03-12)
25
- [NEW] Added option for client to authenticate with IAM token server.
36
- [FIXED] Add `vcapServiceName` configuration option to TS declarations.

cloudant.js

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright © 2015, 2018 IBM Corp. All rights reserved.
1+
// Copyright © 2015, 2019 IBM Corp. All rights reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -25,6 +25,18 @@ const Client = require('./lib/client.js');
2525
// return a URL.
2626
var reconfigure = require('./lib/reconfigure.js');
2727

28+
// Helper function for optional parameter `opts`.
29+
function getCallback(opts, callback) {
30+
if (typeof opts === 'function') {
31+
callback = opts;
32+
opts = {};
33+
}
34+
if (typeof opts === 'undefined') {
35+
opts = {};
36+
}
37+
return {opts, callback};
38+
}
39+
2840
// This IS the Cloudant API. It is mostly nano, with a few functions.
2941
function Cloudant(options, callback) {
3042
debug('Initialize', options);
@@ -150,7 +162,81 @@ function Cloudant(options, callback) {
150162
body: query}, callback);
151163
};
152164

165+
// Partitioned Databases
166+
// ---------------------
167+
168+
function partitionInfo(partitionKey, callback) {
169+
return nano.request(
170+
{db: db, path: '_partition/' + partitionKey}, callback
171+
);
172+
}
173+
174+
function executePartitionedView(partitionKey, ddoc, viewName, meta, qs, callback) {
175+
meta.viewPath = '_partition/' + partitionKey + '/_design/' + ddoc + '/_' +
176+
meta.type + '/' + viewName;
177+
// Note: No need to pass `ddoc` or `viewName` to the `baseView` function
178+
// as they are passed in the `meta.viewPath`.
179+
return nano._use(db).baseView(null, null, meta, qs, callback);
180+
}
181+
182+
function partitionedList(partitionKey, qs0, callback0) {
183+
const {opts, callback} = getCallback(qs0, callback0);
184+
let path = '_partition/' + partitionKey + '/_all_docs';
185+
return nano.request({db: db, path: path, qs: opts}, callback);
186+
}
187+
188+
function partitionedListAsStream(partitionKey, qs) {
189+
let path = '_partition/' + partitionKey + '/_all_docs';
190+
return nano.request(
191+
{db: db, path: path, qs: qs, stream: true}, callback
192+
);
193+
}
194+
195+
function partitionedFind(partitionKey, selector, callback) {
196+
return nano.request({
197+
db: db,
198+
path: '_partition/' + partitionKey + '/_find',
199+
method: 'POST',
200+
body: selector
201+
}, callback);
202+
}
203+
204+
function partitionedFindAsStream(partitionKey, selector) {
205+
return nano.request({
206+
db: db,
207+
path: '_partition/' + partitionKey + '/_find',
208+
method: 'POST',
209+
body: selector,
210+
stream: true
211+
});
212+
}
213+
214+
function partitionedSearch(partitionKey, ddoc, viewName, qs, callback) {
215+
return executePartitionedView(
216+
partitionKey, ddoc, viewName, {type: 'search'}, qs, callback
217+
);
218+
}
219+
220+
function partitionedSearchAsStream(partitionKey, ddoc, viewName, qs) {
221+
return executePartitionedView(
222+
partitionKey, ddoc, viewName, {type: 'search', stream: true}, qs
223+
);
224+
}
225+
226+
function partitionedView(partitionKey, ddoc, viewName, qs, callback) {
227+
return executePartitionedView(
228+
partitionKey, ddoc, viewName, {type: 'view'}, qs, callback
229+
);
230+
}
231+
232+
function partitionedViewAsStream(partitionKey, ddoc, viewName, qs) {
233+
return executePartitionedView(
234+
partitionKey, ddoc, viewName, {type: 'view', stream: true}, qs
235+
);
236+
}
237+
153238
var obj = nano._use(db);
239+
154240
obj.geo = geo;
155241
obj.bulk_get = bulk_get; // eslint-disable-line camelcase
156242
obj.get_security = get_security; // eslint-disable-line camelcase
@@ -159,6 +245,16 @@ function Cloudant(options, callback) {
159245
obj.index.del = index_del; // eslint-disable-line camelcase
160246
obj.find = find;
161247

248+
obj.partitionInfo = partitionInfo;
249+
obj.partitionedFind = partitionedFind;
250+
obj.partitionedFindAsStream = partitionedFindAsStream;
251+
obj.partitionedList = partitionedList;
252+
obj.partitionedListAsStream = partitionedListAsStream;
253+
obj.partitionedSearch = partitionedSearch;
254+
obj.partitionedSearchAsStream = partitionedSearchAsStream;
255+
obj.partitionedView = partitionedView;
256+
obj.partitionedViewAsStream = partitionedViewAsStream;
257+
162258
return obj;
163259
};
164260

test/partitioned_databases.js

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright © 2019 IBM Corp. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/* global describe it before after */
16+
'use strict';
17+
18+
const assert = require('assert');
19+
const async = require('async');
20+
const Cloudant = require('../cloudant.js');
21+
const nock = require('./nock.js');
22+
const uuidv4 = require('uuid/v4'); // random
23+
24+
const ME = process.env.cloudant_username || 'nodejs';
25+
const PASSWORD = process.env.cloudant_password || 'sjedon';
26+
const SERVER = process.env.SERVER_URL || `https://${ME}.cloudant.com`;
27+
const DBNAME = `nodejs-cloudant-${uuidv4()}`;
28+
29+
describe('Partitioned Databases #db', () => {
30+
const partitionKeys = Array.apply(null, {length: 10})
31+
.map(() => { return uuidv4(); });
32+
33+
before(() => {
34+
var mocks = nock(SERVER)
35+
.put(`/${DBNAME}`)
36+
.query({ partitioned: true })
37+
.reply(201, { ok: true });
38+
39+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
40+
return cloudant.db.create(DBNAME, { partitioned: true }).then((body) => {
41+
assert.ok(body.ok);
42+
mocks.done();
43+
});
44+
});
45+
46+
after(() => {
47+
var mocks = nock(SERVER)
48+
.delete(`/${DBNAME}`)
49+
.reply(200, { ok: true });
50+
51+
var cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
52+
return cloudant.db.destroy(DBNAME).then((body) => {
53+
assert.ok(body.ok);
54+
mocks.done();
55+
});
56+
});
57+
58+
it('created a partitioned database', () => {
59+
var mocks = nock(SERVER)
60+
.get(`/${DBNAME}`)
61+
.reply(200, { props: { partitioned: true } });
62+
63+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
64+
return cloudant.db.get(DBNAME).then((body) => {
65+
assert.ok(body.props.partitioned);
66+
mocks.done();
67+
});
68+
});
69+
70+
it('create some partitioned documents', function(done) {
71+
if (!process.env.NOCK_OFF) {
72+
this.skip();
73+
}
74+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
75+
const db = cloudant.db.use(DBNAME);
76+
77+
var q = async.queue(function(task, callback) {
78+
db.bulk({ 'docs': task.docs }).then(callback).catch(done);
79+
}, 10);
80+
q.drain = done;
81+
82+
for (let i in partitionKeys) {
83+
let docs = [];
84+
for (let j = 0; j < 10; j++) {
85+
docs.push({ _id: `${partitionKeys[i]}:doc${j}`, foo: 'bar' });
86+
}
87+
q.push({ 'docs': docs });
88+
}
89+
});
90+
91+
it('get partition information', () => {
92+
const pKey = partitionKeys[0];
93+
94+
var mocks = nock(SERVER)
95+
.get(`/${DBNAME}/_partition/${pKey}`)
96+
.reply(200, { partition: pKey, doc_count: 10 });
97+
98+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
99+
const db = cloudant.db.use(DBNAME);
100+
return db.partitionInfo(pKey).then((body) => {
101+
assert.equal(body.partition, pKey);
102+
assert.equal(body.doc_count, 10);
103+
mocks.done();
104+
});
105+
});
106+
107+
it('get all documents in a partition', () => {
108+
const pKey = partitionKeys[0];
109+
110+
var mocks = nock(SERVER)
111+
.get(`/${DBNAME}/_partition/${pKey}/_all_docs`)
112+
.reply(200, { rows: new Array(10) });
113+
114+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
115+
const db = cloudant.db.use(DBNAME);
116+
return db.partitionedList(pKey).then((body) => {
117+
assert.equal(body.rows.length, 10);
118+
mocks.done();
119+
});
120+
});
121+
122+
describe('Partitioned Query', () => {
123+
before(() => {
124+
if (!process.env.NOCK_OFF) {
125+
return;
126+
}
127+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
128+
const db = cloudant.db.use(DBNAME);
129+
return db.createIndex({ index: { fields: ['foo'] } }).then((body) => {
130+
assert.equal(body.result, 'created');
131+
});
132+
});
133+
134+
it('query a partitioned query', () => {
135+
const pKey = partitionKeys[0];
136+
const selector = { selector: { foo: { $eq: 'bar' } } };
137+
138+
var mocks = nock(SERVER)
139+
.post(`/${DBNAME}/_partition/${pKey}/_find`, selector)
140+
.reply(200, { docs: new Array(10) });
141+
142+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
143+
const db = cloudant.db.use(DBNAME);
144+
return db.partitionedFind(pKey, selector).then((body) => {
145+
assert(body.docs.length, 10);
146+
mocks.done();
147+
});
148+
});
149+
});
150+
151+
describe('Partitioned Search', () => {
152+
before(() => {
153+
if (!process.env.NOCK_OFF) {
154+
return;
155+
}
156+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
157+
const db = cloudant.db.use(DBNAME);
158+
return db.insert({
159+
_id: '_design/mysearch',
160+
options: { partitioned: true },
161+
indexes: {
162+
search1: {
163+
index: 'function(doc) { index("id", doc._id, {"store": true}); }'
164+
}
165+
}
166+
}).then((body) => {
167+
assert.ok(body.ok);
168+
});
169+
});
170+
171+
it('query a partitioned search', () => {
172+
const pKey = partitionKeys[0];
173+
174+
var mocks = nock(SERVER)
175+
.post(`/${DBNAME}/_partition/${pKey}/_design/mysearch/_search/search1`,
176+
{ q: '*:*' })
177+
.reply(200, { rows: new Array(10) });
178+
179+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
180+
const db = cloudant.db.use(DBNAME);
181+
return db.partitionedSearch(pKey, 'mysearch', 'search1', { q: '*:*' }).then((body) => {
182+
assert(body.rows.length, 10);
183+
mocks.done();
184+
});
185+
});
186+
});
187+
188+
describe('Partitioned View', () => {
189+
before(() => {
190+
if (!process.env.NOCK_OFF) {
191+
return;
192+
}
193+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
194+
const db = cloudant.db.use(DBNAME);
195+
return db.insert({
196+
_id: '_design/myview',
197+
options: { partitioned: true },
198+
views: { view1: { map: 'function(doc) { emit(doc._id, 1); }' } }
199+
}).then((body) => {
200+
assert.ok(body.ok);
201+
});
202+
});
203+
204+
it('query a partitioned view', () => {
205+
const pKey = partitionKeys[0];
206+
207+
var mocks = nock(SERVER)
208+
.get(`/${DBNAME}/_partition/${pKey}/_design/myview/_view/view1`)
209+
.reply(200, { rows: new Array(10) });
210+
211+
const cloudant = Cloudant({ url: SERVER, username: ME, password: PASSWORD });
212+
const db = cloudant.db.use(DBNAME);
213+
return db.partitionedView(pKey, 'myview', 'view1').then((body) => {
214+
assert(body.rows.length, 10);
215+
mocks.done();
216+
});
217+
});
218+
});
219+
});

0 commit comments

Comments
 (0)