Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add commitQuorum option to createIndexes command #2345

Merged
merged 4 commits into from
May 5, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
17 changes: 12 additions & 5 deletions lib/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const { removeDocuments, updateDocuments } = require('./operations/common_functi
const AggregateOperation = require('./operations/aggregate');
const BulkWriteOperation = require('./operations/bulk_write');
const CountDocumentsOperation = require('./operations/count_documents');
const CreateIndexOperation = require('./operations/create_index');
const CreateIndexesOperation = require('./operations/create_indexes');
const DeleteManyOperation = require('./operations/delete_many');
const DeleteOneOperation = require('./operations/delete_one');
Expand Down Expand Up @@ -1233,6 +1232,7 @@ Collection.prototype.isCapped = function(options, callback) {
* @param {object} [options.partialFilterExpression] Creates a partial index based on the given filter object (MongoDB 3.2 or higher)
* @param {object} [options.collation] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields).
* @param {ClientSession} [options.session] optional session to use for this operation
* @param {(number|string)} [options.commitQuorum] (MongoDB 4.4. or higher) Specifies how many data-bearing members of a replica set, including the primary, must complete the index builds successfully before the primary marks the indexes as ready. This option accepts the same values for the "w" field in a write concern plus "votingMembers", which indicates all voting data-bearing nodes.
* @param {Collection~resultCallback} [callback] The command result callback
* @returns {Promise} returns Promise if no callback passed
* @example
Expand All @@ -1259,14 +1259,14 @@ Collection.prototype.createIndex = function(fieldOrSpec, options, callback) {
if (typeof options === 'function') (callback = options), (options = {});
options = options || {};

const createIndexOperation = new CreateIndexOperation(
this.s.db,
const createIndexesOperation = new CreateIndexesOperation(
this,
this.collectionName,
fieldOrSpec,
options
);

return executeOperation(this.s.topology, createIndexOperation, callback);
return executeOperation(this.s.topology, createIndexesOperation, callback);
};

/**
Expand All @@ -1281,6 +1281,7 @@ Collection.prototype.createIndex = function(fieldOrSpec, options, callback) {
* @param {Collection~IndexDefinition[]} indexSpecs An array of index specifications to be created
* @param {object} [options] Optional settings
* @param {ClientSession} [options.session] optional session to use for this operation
* @param {(number|string)} [options.commitQuorum] (MongoDB 4.4. or higher) Specifies how many data-bearing members of a replica set, including the primary, must complete the index builds successfully before the primary marks the indexes as ready. This option accepts the same values for the "w" field in a write concern plus "votingMembers", which indicates all voting data-bearing nodes.
* @param {Collection~resultCallback} [callback] The command result callback
* @returns {Promise} returns Promise if no callback passed
* @example
Expand All @@ -1305,9 +1306,15 @@ Collection.prototype.createIndexes = function(indexSpecs, options, callback) {
if (typeof options === 'function') (callback = options), (options = {});

options = options ? Object.assign({}, options) : {};

if (typeof options.maxTimeMS !== 'number') delete options.maxTimeMS;

const createIndexesOperation = new CreateIndexesOperation(this, indexSpecs, options);
const createIndexesOperation = new CreateIndexesOperation(
this,
this.collectionName,
indexSpecs,
options
);

return executeOperation(this.s.topology, createIndexesOperation, callback);
};
Expand Down
7 changes: 4 additions & 3 deletions lib/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const AddUserOperation = require('./operations/add_user');
const CollectionsOperation = require('./operations/collections');
const CommandOperation = require('./operations/command');
const CreateCollectionOperation = require('./operations/create_collection');
const CreateIndexOperation = require('./operations/create_index');
const CreateIndexesOperation = require('./operations/create_indexes');
const { DropCollectionOperation, DropDatabaseOperation } = require('./operations/drop');
const ExecuteDbAdminCommandOperation = require('./operations/execute_db_admin_command');
const IndexInformationOperation = require('./operations/index_information');
Expand Down Expand Up @@ -713,16 +713,17 @@ Db.prototype.executeDbAdminCommand = function(selector, options, callback) {
* @param {string} [options.name] Override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
* @param {object} [options.partialFilterExpression] Creates a partial index based on the given filter object (MongoDB 3.2 or higher)
* @param {ClientSession} [options.session] optional session to use for this operation
* @param {(number|string)} [options.commitQuorum] (MongoDB 4.4. or higher) Specifies how many data-bearing members of a replica set, including the primary, must complete the index builds successfully before the primary marks the indexes as ready. This option accepts the same values for the "w" field in a write concern plus "votingMembers", which indicates all voting data-bearing nodes.
* @param {Db~resultCallback} [callback] The command result callback
* @returns {Promise} returns Promise if no callback passed
*/
Db.prototype.createIndex = function(name, fieldOrSpec, options, callback) {
if (typeof options === 'function') (callback = options), (options = {});
options = options ? Object.assign({}, options) : {};

const createIndexOperation = new CreateIndexOperation(this, name, fieldOrSpec, options);
const createIndexesOperation = new CreateIndexesOperation(this, name, fieldOrSpec, options);

return executeOperation(this.s.topology, createIndexOperation, callback);
return executeOperation(this.s.topology, createIndexesOperation, callback);
};

/**
Expand Down
90 changes: 0 additions & 90 deletions lib/operations/create_index.js

This file was deleted.

125 changes: 90 additions & 35 deletions lib/operations/create_indexes.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,114 @@
'use strict';

const ReadPreference = require('../read_preference');
const { Aspect, defineAspects, OperationBase } = require('./operation');
const { executeCommand } = require('./db_ops');
const { Aspect, defineAspects } = require('./operation');
const { MongoError } = require('../error');
const CommandOperationV2 = require('./command_v2');
const { maxWireVersion, parseIndexOptions } = require('../utils');

class CreateIndexesOperation extends OperationBase {
constructor(collection, indexSpecs, options) {
super(options);
const validIndexOptions = new Set([
'unique',
'partialFilterExpression',
'sparse',
'background',
'expireAfterSeconds',
'storageEngine',
'collation'
]);

class CreateIndexesOperation extends CommandOperationV2 {
/**
* @ignore
*/
constructor(parent, collection, indexes, options) {
super(parent, options);
this.collection = collection;
this.indexSpecs = indexSpecs;

// createIndex can be called with a variety of styles:
// coll.createIndex('a');
// coll.createIndex({ a: 1 });
// coll.createIndex([['a', 1]]);
// createIndexes is always called with an array of index spec objects
if (!Array.isArray(indexes) || Array.isArray(indexes[0])) {
this.onlyReturnNameOfCreatedIndex = true;
// TODO: remove in v4 (breaking change); make createIndex return full response as createIndexes does
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make a Jira ticket for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const indexParameters = parseIndexOptions(indexes);
// Generate the index name
const name = typeof options.name === 'string' ? options.name : indexParameters.name;
// Set up the index
const indexSpec = { name, key: indexParameters.fieldHash };
// merge valid index options into the index spec
for (let optionName in options) {
if (validIndexOptions.has(optionName)) {
indexSpec[optionName] = options[optionName];
}
}
this.indexes = [indexSpec];
return;
}

this.indexes = indexes;
}

execute(callback) {
const coll = this.collection;
const indexSpecs = this.indexSpecs;
let options = this.options;
/**
* @ignore
*/
execute(server, callback) {
const options = this.options;
const indexes = this.indexes;

const capabilities = coll.s.topology.capabilities();
const serverWireVersion = maxWireVersion(server);

// Ensure we generate the correct name if the parameter is not set
for (let i = 0; i < indexSpecs.length; i++) {
if (indexSpecs[i].name == null) {
const keys = [];
for (let i = 0; i < indexes.length; i++) {
// Did the user pass in a collation, check if our write server supports it
if (indexes[i].collation && serverWireVersion < 5) {
callback(
new MongoError(
`Server ${server.name}, which reports wire version ${serverWireVersion}, does not support collation`
)
);
return;
}

// Did the user pass in a collation, check if our write server supports it
if (indexSpecs[i].collation && capabilities && !capabilities.commandsTakeCollation) {
return callback(new MongoError('server/primary/mongos does not support collation'));
}
if (indexes[i].name == null) {
const keys = [];

for (let name in indexSpecs[i].key) {
keys.push(`${name}_${indexSpecs[i].key[name]}`);
for (let name in indexes[i].key) {
keys.push(`${name}_${indexes[i].key[name]}`);
}

// Set the name
indexSpecs[i].name = keys.join('_');
indexes[i].name = keys.join('_');
}
}

const cmd = { createIndexes: this.collection, indexes };

if (options.commitQuorum != null) {
if (serverWireVersion < 9) {
callback(
new MongoError('`commitQuorum` option for `createIndexes` not supported on servers < 4.4')
);
return;
}
cmd.commitQuorum = options.commitQuorum;
}

options = Object.assign({}, options, { readPreference: ReadPreference.PRIMARY });

// Execute the index
executeCommand(
coll.s.db,
{
createIndexes: coll.collectionName,
indexes: indexSpecs
},
options,
callback
);
// collation is set on each index, it should not be defined at the root
this.options.collation = undefined;

super.executeCommand(server, cmd, (err, result) => {
if (err) {
callback(err);
return;
}

callback(null, this.onlyReturnNameOfCreatedIndex ? indexes[0].name : result);
});
}
}

defineAspects(CreateIndexesOperation, Aspect.WRITE_OPERATION);
defineAspects(CreateIndexesOperation, [Aspect.WRITE_OPERATION, Aspect.EXECUTE_WITH_SELECTION]);

module.exports = CreateIndexesOperation;
4 changes: 2 additions & 2 deletions test/functional/collations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ describe('Collation', function() {
.then(() => Promise.reject('should not succeed'))
.catch(err => {
expect(err).to.exist;
expect(err.message).to.equal('server/primary/mongos does not support collation');
expect(err.message).to.match(/does not support collation$/);
})
.then(() => client.close());
});
Expand Down Expand Up @@ -750,7 +750,7 @@ describe('Collation', function() {
.createIndexes([{ key: { a: 1 }, collation: { caseLevel: true } }])
.then(() => Promise.reject('should not succeed'))
.catch(err => {
expect(err.message).to.equal('server/primary/mongos does not support collation');
expect(err.message).to.match(/does not support collation$/);
return client.close();
});
});
Expand Down
Loading