Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions packages/i18n/src/locales/en_US.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,16 @@ const translations = {
description: 'Returns an interface to access the query plan cache for a collection. The interface provides methods to view and clear the query plan cache.',
example: 'db.coll.getPlanCache()'
},
validate: {
link: 'https://docs.mongodb.com/manual/reference/method/db.collection.validate',
description: 'Calls the validate command. Default full value is false',
example: 'db.validate(<full>)'
},
mapReduce: {
link: 'https://docs.mongodb.com/manual/reference/method/db.collection.mapReduce',
description: 'Calls the mapReduce command',
example: 'db.mapReduce(mapFn, reduceFn, options)'
}
}
}
},
Expand Down
89 changes: 89 additions & 0 deletions packages/shell-api/src/collection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -958,5 +958,94 @@ describe('Collection', () => {
expect(pc._asPrintable()).to.equal('PlanCache for collection coll1.');
});
});
describe('validate', () => {
it('calls serviceProvider.runCommand on the collection default', async() => {
serviceProvider.runCommandWithCheck.resolves({ ok: 1 });
await collection.validate();
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
database._name,
{
validate: collection._name,
full: false
}
);
});
it('calls serviceProvider.runCommand on the collection with options', async() => {
await collection.validate(true);
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
database._name,
{
validate: collection._name,
full: true
}
);
});

it('returns whatever serviceProvider.runCommand returns', async() => {
const expectedResult = { ok: 1 };
serviceProvider.runCommandWithCheck.resolves(expectedResult);
const result = await collection.validate();
expect(result).to.deep.equal(expectedResult);
});

it('throws if serviceProvider.runCommand rejects', async() => {
const expectedError = new Error();
serviceProvider.runCommandWithCheck.rejects(expectedError);
const catchedError = await collection.validate()
.catch(e => e);
expect(catchedError).to.equal(expectedError);
});
});
describe('mapReduce', () => {
let mapFn;
let reduceFn;
beforeEach(() => {
mapFn = function(): void {};
reduceFn = function(keyCustId, valuesPrices): any {
return valuesPrices.reduce((t, s) => (t + s));
};
});
it('calls serviceProvider.mapReduce on the collection with js args', async() => {
serviceProvider.runCommandWithCheck.resolves({ ok: 1 });
await collection.mapReduce(mapFn, reduceFn, { out: 'map_reduce_example' });
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
database._name,
{
mapReduce: collection._name,
map: mapFn,
reduce: reduceFn,
out: 'map_reduce_example'
}
);
});
it('calls serviceProvider.runCommand on the collection with string args', async() => {
serviceProvider.runCommandWithCheck.resolves({ ok: 1 });
await collection.mapReduce(mapFn.toString(), reduceFn.toString(), { out: 'map_reduce_example' });
expect(serviceProvider.runCommandWithCheck).to.have.been.calledWith(
database._name,
{
mapReduce: collection._name,
map: mapFn.toString(),
reduce: reduceFn.toString(),
out: 'map_reduce_example'
}
);
});

it('returns whatever serviceProvider.mapReduce returns', async() => {
const expectedResult = { ok: 1 };
serviceProvider.runCommandWithCheck.resolves(expectedResult);
const result = await collection.mapReduce(mapFn, reduceFn, { out: { inline: 1 } });
expect(result).to.deep.equal(expectedResult);
});

it('throws if serviceProvider.mapReduce rejects', async() => {
const expectedError = new Error();
serviceProvider.runCommandWithCheck.rejects(expectedError);
const catchedError = await collection.mapReduce(mapFn, reduceFn, { out: { inline: 1 } })
.catch(e => e);
expect(catchedError).to.equal(expectedError);
});
});
});
});
37 changes: 37 additions & 0 deletions packages/shell-api/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1364,4 +1364,41 @@ export default class Collection extends ShellApiClass {
this._emitCollectionApiCall('getPlanCache');
return new PlanCache(this);
}

@returnsPromise
async mapReduce(map: any, reduce: any, optionsOrOutString: Document | string): Promise<any> {
assertArgsDefined(map, reduce, optionsOrOutString);
this._emitCollectionApiCall('mapReduce', { map, reduce, out: optionsOrOutString });

let cmd = {
mapReduce: this._name,
map: map,
reduce: reduce
} as any;

if (typeof optionsOrOutString === 'string') {
cmd.out = optionsOrOutString;
} else if (optionsOrOutString.out === undefined) {
throw new MongoshInvalidInputError('Missing \'out\' option');
} else {
cmd = { ...cmd, ...optionsOrOutString };
}

return await this._mongo._serviceProvider.runCommandWithCheck(
this._database._name,
cmd
);
}

@returnsPromise
async validate(full = false): Promise<Document> {
this._emitCollectionApiCall('validate', { full });
return await this._mongo._serviceProvider.runCommandWithCheck(
this._database._name,
{
validate: this._name,
full: full
}
);
}
}
105 changes: 105 additions & 0 deletions packages/shell-api/src/integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/camelcase */
import { expect } from 'chai';
import { CliServiceProvider } from '../../service-provider-server'; // avoid cyclic dep just for test
import ShellInternalState from './shell-internal-state';
Expand Down Expand Up @@ -66,6 +67,22 @@ describe('Shell API (integration)', function() {
await collection.find( { quantity: { $gte: 5 }, type: 'apparel' } ).toArray();
};

const loadMRExample = async(collection): Promise<any> => {
const res = await collection.insertMany([
{ _id: 1, cust_id: 'Ant O. Knee', ord_date: new Date('2020-03-01'), price: 25, items: [ { sku: 'oranges', qty: 5, price: 2.5 }, { sku: 'apples', qty: 5, price: 2.5 } ], status: 'A' },
{ _id: 2, cust_id: 'Ant O. Knee', ord_date: new Date('2020-03-08'), price: 70, items: [ { sku: 'oranges', qty: 8, price: 2.5 }, { sku: 'chocolates', qty: 5, price: 10 } ], status: 'A' },
{ _id: 3, cust_id: 'Busby Bee', ord_date: new Date('2020-03-08'), price: 50, items: [ { sku: 'oranges', qty: 10, price: 2.5 }, { sku: 'pears', qty: 10, price: 2.5 } ], status: 'A' },
{ _id: 4, cust_id: 'Busby Bee', ord_date: new Date('2020-03-18'), price: 25, items: [ { sku: 'oranges', qty: 10, price: 2.5 } ], status: 'A' },
{ _id: 5, cust_id: 'Busby Bee', ord_date: new Date('2020-03-19'), price: 50, items: [ { sku: 'chocolates', qty: 5, price: 10 } ], status: 'A' },
{ _id: 6, cust_id: 'Cam Elot', ord_date: new Date('2020-03-19'), price: 35, items: [ { sku: 'carrots', qty: 10, price: 1.0 }, { sku: 'apples', qty: 10, price: 2.5 } ], status: 'A' },
{ _id: 7, cust_id: 'Cam Elot', ord_date: new Date('2020-03-20'), price: 25, items: [ { sku: 'oranges', qty: 10, price: 2.5 } ], status: 'A' },
{ _id: 8, cust_id: 'Don Quis', ord_date: new Date('2020-03-20'), price: 75, items: [ { sku: 'chocolates', qty: 5, price: 10 }, { sku: 'apples', qty: 10, price: 2.5 } ], status: 'A' },
{ _id: 9, cust_id: 'Don Quis', ord_date: new Date('2020-03-20'), price: 55, items: [ { sku: 'carrots', qty: 5, price: 1.0 }, { sku: 'apples', qty: 10, price: 2.5 }, { sku: 'oranges', qty: 10, price: 2.5 } ], status: 'A' },
{ _id: 10, cust_id: 'Don Quis', ord_date: new Date('2020-03-23'), price: 25, items: [ { sku: 'oranges', qty: 10, price: 2.5 } ], status: 'A' }
]);
expect(res.acknowledged).to.equal(1);
};

before(async() => {
serviceProvider = await CliServiceProvider.connect(connectionString);
});
Expand Down Expand Up @@ -1348,4 +1365,92 @@ describe('Shell API (integration)', function() {
});
});
});
describe('mapReduce', () => {
it('accepts function args and collection name as string', async() => {
await loadMRExample(collection);
const mapFn = `function() {
emit(this.cust_id, this.price);
};`;
const reduceFn = function(keyCustId, valuesPrices): any {
return valuesPrices.reduce((s, t) => s + t);
};
const result = await collection.mapReduce(mapFn, reduceFn, 'map_reduce_example');
expect(result.ok).to.equal(1);
const outRes = await database.map_reduce_example.find().sort({ _id: 1 }).toArray();
expect(outRes).to.deep.equal([
{ '_id': 'Ant O. Knee', 'value': 95 },
{ '_id': 'Busby Bee', 'value': 125 },
{ '_id': 'Cam Elot', 'value': 60 },
{ '_id': 'Don Quis', 'value': 155 }
]);
});
it('accepts string args and collection name as string', async() => {
await loadMRExample(collection);
const mapFn = `function() {
emit(this.cust_id, this.price);
};`;
const reduceFn = function(keyCustId, valuesPrices): any {
return valuesPrices.reduce((s, t) => s + t);
};
const result = await collection.mapReduce(mapFn, reduceFn.toString(), 'map_reduce_example');
expect(result.ok).to.equal(1);
expect(result.result).to.equal('map_reduce_example');
const outRes = await database.map_reduce_example.find().sort({ _id: 1 }).toArray();
expect(outRes).to.deep.equal([
{ '_id': 'Ant O. Knee', 'value': 95 },
{ '_id': 'Busby Bee', 'value': 125 },
{ '_id': 'Cam Elot', 'value': 60 },
{ '_id': 'Don Quis', 'value': 155 }
]);
});
it('accepts inline as option', async() => {
await loadMRExample(collection);
const mapFn = `function() {
emit(this.cust_id, this.price);
};`;
const reduceFn = function(keyCustId, valuesPrices): any {
return valuesPrices.reduce((s, t) => s + t);
};
const result = await collection.mapReduce(mapFn, reduceFn.toString(), {
out: { inline: 1 }
});
expect(result.ok).to.equal(1);
expect(result.results.map(k => k._id).sort()).to.deep.equal([
'Ant O. Knee',
'Busby Bee',
'Cam Elot',
'Don Quis'
]);
expect(result.results.map(k => k.value).sort()).to.deep.equal([
125,
155,
60,
95
]);
});
it('accepts finalize as option', async() => {
await loadMRExample(collection);
const mapFn = `function() {
emit(this.cust_id, this.price);
};`;
const reduceFn = function(keyCustId, valuesPrices): any {
return valuesPrices.reduce((s, t) => s + t);
};
const finalizeFn = function(): any {
return 1;
};
const result = await collection.mapReduce(mapFn, reduceFn.toString(), {
out: { inline: 1 },
finalize: finalizeFn
});
expect(result.ok).to.equal(1);
expect(result.results.map(k => k._id).sort()).to.deep.equal([
'Ant O. Knee',
'Busby Bee',
'Cam Elot',
'Don Quis'
]);
expect(result.results.map(k => k.value)).to.deep.equal([1, 1, 1, 1]);
});
});
});