"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexInformationOperation = exports.IndexExistsOperation = exports.ListIndexesCursor = exports.ListIndexesOperation = exports.DropIndexesOperation = exports.DropIndexOperation = exports.EnsureIndexOperation = exports.CreateIndexOperation = exports.CreateIndexesOperation = exports.IndexesOperation = void 0;
const common_functions_1 = require("./common_functions");
const operation_1 = require("./operation");
const error_1 = require("../error");
const utils_1 = require("../utils");
const command_1 = require("./command");
const read_preference_1 = require("../read_preference");
const abstract_cursor_1 = require("../cursor/abstract_cursor");
const execute_operation_1 = require("./execute_operation");
const LIST_INDEXES_WIRE_VERSION = 3;
const VALID_INDEX_OPTIONS = new Set([
    'background',
    'unique',
    'name',
    'partialFilterExpression',
    'sparse',
    'hidden',
    'expireAfterSeconds',
    'storageEngine',
    'collation',
    'version',
    // text indexes
    'weights',
    'default_language',
    'language_override',
    'textIndexVersion',
    // 2d-sphere indexes
    '2dsphereIndexVersion',
    // 2d indexes
    'bits',
    'min',
    'max',
    // geoHaystack Indexes
    'bucketSize',
    // wildcard indexes
    'wildcardProjection'
]);
function makeIndexSpec(indexSpec, options) {
    const indexParameters = utils_1.parseIndexOptions(indexSpec);
    // Generate the index name
    const name = typeof options.name === 'string' ? options.name : indexParameters.name;
    // Set up the index
    const finalIndexSpec = { name, key: indexParameters.fieldHash };
    // merge valid index options into the index spec
    for (const optionName in options) {
        if (VALID_INDEX_OPTIONS.has(optionName)) {
            finalIndexSpec[optionName] = options[optionName];
        }
    }
    return finalIndexSpec;
}
/** @internal */
class IndexesOperation extends operation_1.AbstractOperation {
    constructor(collection, options) {
        super(options);
        this.options = options;
        this.collection = collection;
    }
    execute(server, session, callback) {
        const coll = this.collection;
        const options = this.options;
        common_functions_1.indexInformation(coll.s.db, coll.collectionName, { full: true, ...options, readPreference: this.readPreference, session }, callback);
    }
}
exports.IndexesOperation = IndexesOperation;
/** @internal */
class CreateIndexesOperation extends command_1.CommandOperation {
    constructor(parent, collectionName, indexes, options) {
        super(parent, options);
        this.options = options !== null && options !== void 0 ? options : {};
        this.collectionName = collectionName;
        this.indexes = indexes;
    }
    execute(server, session, callback) {
        const options = this.options;
        const indexes = this.indexes;
        const serverWireVersion = utils_1.maxWireVersion(server);
        // Ensure we generate the correct name if the parameter is not set
        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 error_1.MongoCompatibilityError(`Server ${server.name}, which reports wire version ${serverWireVersion}, ` +
                    'does not support collation'));
                return;
            }
            if (indexes[i].name == null) {
                const keys = [];
                for (const name in indexes[i].key) {
                    keys.push(`${name}_${indexes[i].key[name]}`);
                }
                // Set the name
                indexes[i].name = keys.join('_');
            }
        }
        const cmd = { createIndexes: this.collectionName, indexes };
        if (options.commitQuorum != null) {
            if (serverWireVersion < 9) {
                callback(new error_1.MongoCompatibilityError('Option `commitQuorum` for `createIndexes` not supported on servers < 4.4'));
                return;
            }
            cmd.commitQuorum = options.commitQuorum;
        }
        // collation is set on each index, it should not be defined at the root
        this.options.collation = undefined;
        super.executeCommand(server, session, cmd, err => {
            if (err) {
                callback(err);
                return;
            }
            const indexNames = indexes.map(index => index.name || '');
            callback(undefined, indexNames);
        });
    }
}
exports.CreateIndexesOperation = CreateIndexesOperation;
/** @internal */
class CreateIndexOperation extends CreateIndexesOperation {
    constructor(parent, collectionName, indexSpec, options) {
        // 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
        super(parent, collectionName, [makeIndexSpec(indexSpec, options)], options);
    }
    execute(server, session, callback) {
        super.execute(server, session, (err, indexNames) => {
            if (err || !indexNames)
                return callback(err);
            return callback(undefined, indexNames[0]);
        });
    }
}
exports.CreateIndexOperation = CreateIndexOperation;
/** @internal */
class EnsureIndexOperation extends CreateIndexOperation {
    constructor(db, collectionName, indexSpec, options) {
        super(db, collectionName, indexSpec, options);
        this.readPreference = read_preference_1.ReadPreference.primary;
        this.db = db;
        this.collectionName = collectionName;
    }
    execute(server, session, callback) {
        const indexName = this.indexes[0].name;
        const cursor = this.db.collection(this.collectionName).listIndexes({ session });
        cursor.toArray((err, indexes) => {
            /// ignore "NamespaceNotFound" errors
            if (err && err.code !== error_1.MONGODB_ERROR_CODES.NamespaceNotFound) {
                return callback(err);
            }
            if (indexes) {
                indexes = Array.isArray(indexes) ? indexes : [indexes];
                if (indexes.some(index => index.name === indexName)) {
                    callback(undefined, indexName);
                    return;
                }
            }
            super.execute(server, session, callback);
        });
    }
}
exports.EnsureIndexOperation = EnsureIndexOperation;
/** @internal */
class DropIndexOperation extends command_1.CommandOperation {
    constructor(collection, indexName, options) {
        super(collection, options);
        this.options = options !== null && options !== void 0 ? options : {};
        this.collection = collection;
        this.indexName = indexName;
    }
    execute(server, session, callback) {
        const cmd = { dropIndexes: this.collection.collectionName, index: this.indexName };
        super.executeCommand(server, session, cmd, callback);
    }
}
exports.DropIndexOperation = DropIndexOperation;
/** @internal */
class DropIndexesOperation extends DropIndexOperation {
    constructor(collection, options) {
        super(collection, '*', options);
    }
    execute(server, session, callback) {
        super.execute(server, session, err => {
            if (err)
                return callback(err, false);
            callback(undefined, true);
        });
    }
}
exports.DropIndexesOperation = DropIndexesOperation;
/** @internal */
class ListIndexesOperation extends command_1.CommandOperation {
    constructor(collection, options) {
        super(collection, options);
        this.options = options !== null && options !== void 0 ? options : {};
        this.collectionNamespace = collection.s.namespace;
    }
    execute(server, session, callback) {
        const serverWireVersion = utils_1.maxWireVersion(server);
        if (serverWireVersion < LIST_INDEXES_WIRE_VERSION) {
            const systemIndexesNS = this.collectionNamespace.withCollection('system.indexes');
            const collectionNS = this.collectionNamespace.toString();
            server.query(systemIndexesNS, { query: { ns: collectionNS } }, { ...this.options, readPreference: this.readPreference }, callback);
            return;
        }
        const cursor = this.options.batchSize ? { batchSize: this.options.batchSize } : {};
        super.executeCommand(server, session, { listIndexes: this.collectionNamespace.collection, cursor }, callback);
    }
}
exports.ListIndexesOperation = ListIndexesOperation;
/** @public */
class ListIndexesCursor extends abstract_cursor_1.AbstractCursor {
    constructor(collection, options) {
        super(utils_1.getTopology(collection), collection.s.namespace, options);
        this.parent = collection;
        this.options = options;
    }
    clone() {
        return new ListIndexesCursor(this.parent, {
            ...this.options,
            ...this.cursorOptions
        });
    }
    /** @internal */
    _initialize(session, callback) {
        const operation = new ListIndexesOperation(this.parent, {
            ...this.cursorOptions,
            ...this.options,
            session
        });
        execute_operation_1.executeOperation(utils_1.getTopology(this.parent), operation, (err, response) => {
            if (err || response == null)
                return callback(err);
            // TODO: NODE-2882
            callback(undefined, { server: operation.server, session, response });
        });
    }
}
exports.ListIndexesCursor = ListIndexesCursor;
/** @internal */
class IndexExistsOperation extends operation_1.AbstractOperation {
    constructor(collection, indexes, options) {
        super(options);
        this.options = options;
        this.collection = collection;
        this.indexes = indexes;
    }
    execute(server, session, callback) {
        const coll = this.collection;
        const indexes = this.indexes;
        common_functions_1.indexInformation(coll.s.db, coll.collectionName, { ...this.options, readPreference: this.readPreference, session }, (err, indexInformation) => {
            // If we have an error return
            if (err != null)
                return callback(err);
            // Let's check for the index names
            if (!Array.isArray(indexes))
                return callback(undefined, indexInformation[indexes] != null);
            // Check in list of indexes
            for (let i = 0; i < indexes.length; i++) {
                if (indexInformation[indexes[i]] == null) {
                    return callback(undefined, false);
                }
            }
            // All keys found return true
            return callback(undefined, true);
        });
    }
}
exports.IndexExistsOperation = IndexExistsOperation;
/** @internal */
class IndexInformationOperation extends operation_1.AbstractOperation {
    constructor(db, name, options) {
        super(options);
        this.options = options !== null && options !== void 0 ? options : {};
        this.db = db;
        this.name = name;
    }
    execute(server, session, callback) {
        const db = this.db;
        const name = this.name;
        common_functions_1.indexInformation(db, name, { ...this.options, readPreference: this.readPreference, session }, callback);
    }
}
exports.IndexInformationOperation = IndexInformationOperation;
operation_1.defineAspects(ListIndexesOperation, [
    operation_1.Aspect.READ_OPERATION,
    operation_1.Aspect.RETRYABLE,
    operation_1.Aspect.CURSOR_CREATING
]);
operation_1.defineAspects(CreateIndexesOperation, [operation_1.Aspect.WRITE_OPERATION]);
operation_1.defineAspects(CreateIndexOperation, [operation_1.Aspect.WRITE_OPERATION]);
operation_1.defineAspects(EnsureIndexOperation, [operation_1.Aspect.WRITE_OPERATION]);
operation_1.defineAspects(DropIndexOperation, [operation_1.Aspect.WRITE_OPERATION]);
operation_1.defineAspects(DropIndexesOperation, [operation_1.Aspect.WRITE_OPERATION]);
//# sourceMappingURL=indexes.js.map