<commit_before>const { promisify } = require('es6-promisify');
const debug = require('debug')('cosa:db');
const defaults = require('defaults');
const EJSON = require('mongodb-extended-json');
const EventEmitter = require('events').EventEmitter;
const MongoClient = require('mongodb').MongoClient;
// const mongoConnect = promisify(MongoClient.connect);
const { curry, split, last, pipe } = require('omnibelt');
const RESULT_FIELDS = [ 'insertedCount', 'insertedIds', 'matchedCount', 'modifiedCount', 'upsertedCount', 'deletedCount', 'upsertedIds' ];
const getDbName = pipe(split('/'), last);

const normalizeResult = (r) => {
  const result = {
    result: r.result,
    ops: r.ops
  };
  RESULT_FIELDS.forEach((type) => {
    if ('undefined' !== typeof r[type]) {
      result[type] = r[type];
    }
  });
  return result;
};

const actionOneOrMany = curry(async (action, collection, isMultiple, ...params) => {
  const type = isMultiple ? `${action}Many` : `${action}One`;
  debug(`db.${collection.collectionName}.${type}`, ...params);
  const r = await collection[type](...params);
  return normalizeResult(r);
});

const insertOneOrMany = actionOneOrMany('insert');
const updateOneOrMany = actionOneOrMany('update');
const removeOneOrMany = actionOneOrMany('remove');

/**
 * Singleton that provides methods for connecting to a MongoDB collection.
 * @class Database
 */
class Database extends EventEmitter {
  constructor() {
    super();
    this._db = null;
    this._client = null;
    this._connectionStatus = 'disconnected';
    Object.defineProperty(this, 'connectionStatus', {
      enumerable: true,
      get: function() {
        return this._connectionStatus;
      }
    });
  }

  /**
   * Initialize the database connection.
   * @param {string} [uri] - URI to the database. If no URI is provided, the value
     *    of `process.env.COSA_DB_URI` is used. See
     *    https://docs.mongodb.com/manual/reference/connection-string/ for
     *    information on the URI format.
   * @see {@link init}
   * @returns {Promise} returns the db when connected
   */
  async init(uri, options) {
    options = options || {};
    this._uri = uri || process.env.COSA_DB_URI;
    if (!(/mongodb:\/\//).test(this._uri)) {
      throw new Error('invalid mongodb uri');
    }
    // so we dont mess up people environment variables.
    const dbName = options.dbName || getDbName(this._uri);
    debug(`connecting to database: ${this._uri}`);
    this._connectionStatus = 'connecting';
    this.emit('connecting');
    try {
      this._client = await MongoClient.connect(this._uri, { useNewUrlParser: true });
      this._db = await this._client.db(dbName);
    } catch (err) {
      debug('failed to connect', err);
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      throw err;
    }
    this._connectionStatus = 'connected';
    this._client.on('close', () => {
      debug('database connection closed');
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      this.emit('disconnect');
    });
    this.emit('connect', this);
    debug('connected to database');
    return this;
  }

  /**
   * Fetch a specific database collection.
   * @param {string} name - Collection name.
   * @param {object} [options] - Optional collection settings.
   * @returns {Promise} resolves with the connection
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html#collection}
   */
  async collection(name, options) {
    if (this._connectionStatus === 'connected') {
      debug(`loading collection ${name}`);
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    } else if (this._connectionStatus === 'connecting') {
      debug(`waiting on connection before loading collection ${name}`);
      return new Promise((resolve) => {
        this.on('connect', async () => {
          const collection = await this._db.collection(name, options);
          debug(`collection ${name} loaded`);
          return resolve(collection);
        });
      });
    } else {
      await this.init();
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    }
  }

  /**
   * Fetches documents from a collection with the given query.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - MongoDB query object.
   * @param {object} [options] - MongoDB query options.
   * @param {object} [options.fields] - The fields to return in the query. Object of fields to include or exclude (not both), {'a':1
   * @param {(object|Array)} [options.sort] - Set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
   * @param {number} [options.skip=0] - Set to skip N documents ahead in your query (useful for pagination).
   * @param {object} [options.limit=1000] - Sets the limit of documents returned in the query.
   * @param {object} [options.count=false] - Should the count of items be returned instead of the items themselves.
   * @param {object} [options.findOne=false] - Should a single item be returned.
   * @returns {Cursor} returns Cursor object
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Cursor.html}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#find}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#findOne}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#count}
   */
  async find(collectionName, query, options) {
    options = defaults(options, {
      fields: undefined,
      sort: undefined,
      skip: undefined,
      limit: options.count ? null : 1000,
      count: false,
      findOne: false
    });
    query = EJSON.deserialize(query);
    debug('getting db collection');
    const collection = await this.collection(collectionName);
    if (options.count) {
      const countOptions = { limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.count`, query, countOptions);
      // this is broken...and I know why
      return collection.countDocuments(query, options);
    } else if (options.findOne) {
      const findOptions = { fields: options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.findOne`, query, findOptions);
      return collection.findOne(query, options);
    } else {
      const findOptions = { fields: options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.find (toArray)`, query, findOptions);
      return collection.find(query, options);<commit_msg>updating comment<commit_after>const { promisify } = require('es6-promisify');
const debug = require('debug')('cosa:db');
const defaults = require('defaults');
const EJSON = require('mongodb-extended-json');
const EventEmitter = require('events').EventEmitter;
const MongoClient = require('mongodb').MongoClient;
// const mongoConnect = promisify(MongoClient.connect);
const { curry, split, last, pipe } = require('omnibelt');
const RESULT_FIELDS = [ 'insertedCount', 'insertedIds', 'matchedCount', 'modifiedCount', 'upsertedCount', 'deletedCount', 'upsertedIds' ];
const getDbName = pipe(split('/'), last);

const normalizeResult = (r) => {
  const result = {
    result: r.result,
    ops: r.ops
  };
  RESULT_FIELDS.forEach((type) => {
    if ('undefined' !== typeof r[type]) {
      result[type] = r[type];
    }
  });
  return result;
};

const actionOneOrMany = curry(async (action, collection, isMultiple, ...params) => {
  const type = isMultiple ? `${action}Many` : `${action}One`;
  debug(`db.${collection.collectionName}.${type}`, ...params);
  const r = await collection[type](...params);
  return normalizeResult(r);
});

const insertOneOrMany = actionOneOrMany('insert');
const updateOneOrMany = actionOneOrMany('update');
const removeOneOrMany = actionOneOrMany('remove');

/**
 * Singleton that provides methods for connecting to a MongoDB collection.
 * @class Database
 */
class Database extends EventEmitter {
  constructor() {
    super();
    this._db = null;
    this._client = null;
    this._connectionStatus = 'disconnected';
    Object.defineProperty(this, 'connectionStatus', {
      enumerable: true,
      get: function() {
        return this._connectionStatus;
      }
    });
  }

  /**
   * Initialize the database connection.
   * @param {string} [uri] - URI to the database. If no URI is provided, the value
     *    of `process.env.COSA_DB_URI` is used. See
     *    https://docs.mongodb.com/manual/reference/connection-string/ for
     *    information on the URI format.
   * @see {@link init}
   * @returns {Promise} returns the db when connected
   */
  async init(uri, options) {
    options = options || {};
    this._uri = uri || process.env.COSA_DB_URI;
    if (!(/mongodb:\/\//).test(this._uri)) {
      throw new Error('invalid mongodb uri');
    }
    // so we dont mess up people environment variables.
    const dbName = options.dbName || getDbName(this._uri);
    debug(`connecting to database: ${this._uri}`);
    this._connectionStatus = 'connecting';
    this.emit('connecting');
    try {
      this._client = await MongoClient.connect(this._uri, { useNewUrlParser: true });
      this._db = await this._client.db(dbName);
    } catch (err) {
      debug('failed to connect', err);
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      throw err;
    }
    this._connectionStatus = 'connected';
    this._client.on('close', () => {
      debug('database connection closed');
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      this.emit('disconnect');
    });
    this.emit('connect', this);
    debug('connected to database');
    return this;
  }

  /**
   * Fetch a specific database collection.
   * @param {string} name - Collection name.
   * @param {object} [options] - Optional collection settings.
   * @returns {Promise} resolves with the connection
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html#collection}
   */
  async collection(name, options) {
    if (this._connectionStatus === 'connected') {
      debug(`loading collection ${name}`);
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    } else if (this._connectionStatus === 'connecting') {
      debug(`waiting on connection before loading collection ${name}`);
      return new Promise((resolve) => {
        this.on('connect', async () => {
          const collection = await this._db.collection(name, options);
          debug(`collection ${name} loaded`);
          return resolve(collection);
        });
      });
    } else {
      await this.init();
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    }
  }

  /**
   * Fetches documents from a collection with the given query.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - MongoDB query object.
   * @param {object} [options] - MongoDB query options.
   * @param {object} [options.fields] - The fields to return in the query. Object of fields to include or exclude (not both), {'a':1
   * @param {(object|Array)} [options.sort] - Set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
   * @param {number} [options.skip=0] - Set to skip N documents ahead in your query (useful for pagination).
   * @param {object} [options.limit=1000] - Sets the limit of documents returned in the query.
   * @param {object} [options.count=false] - Should the count of items be returned instead of the items themselves.
   * @param {object} [options.findOne=false] - Should a single item be returned.
   * @returns {Cursor} returns Cursor object
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Cursor.html}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#find}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#findOne}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#count}
   */
  async find(collectionName, query, options) {
    options = defaults(options, {
      fields: undefined,
      sort: undefined,
      skip: undefined,
      limit: options.count ? null : 1000,
      count: false,
      findOne: false
    });
    query = EJSON.deserialize(query);
    debug('getting db collection');
    const collection = await this.collection(collectionName);
    if (options.count) {
      const countOptions = { limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.count`, query, countOptions);
      // this is broken...https://github.com/mongodb/node-mongodb-native/pull/1767
      return collection.countDocuments(query, options);
    } else if (options.findOne) {
      const findOptions = { fields: options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.findOne`, query, findOptions);
      return collection.findOne(query, options);
    } else {
      const findOptions = { fields: options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.find (toArray)`, query, findOptions);
      return collection.find(query, options);
{'fraction_non_alphanumeric': 0.14436133904582094, 'fraction_numerical': 0.0034855856510057365, 'mean_word_length': 3.3692893401015227, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2386024', 'n_tokens_mistral': 4055, 'n_tokens_neox': 3998, 'n_words': 1081}