docs: function resolveKeyColumns(leftDf, rightDf, opts) {
    // Resolve key columns to integer indices
    var leftKeyIdxVec, rightKeyIdxVec;
    if (isUndefined(opts) ||
      (isUndefined(opts.by) && isUndefined(opts.leftBy) &&
        isUndefined(opts.rightBy))) {
  
      var commonNames = leftDf.names().s(leftDf.names().isIn(rightDf.names()));
      if (commonNames.nUnique() !== commonNames.size()) {
        throw new Error('duplicate names found for key columns');
      }
      leftKeyIdxVec =
        columnIndexing(commonNames, leftDf._names, leftDf._dtypesVector());
      rightKeyIdxVec =
        columnIndexing(commonNames, rightDf._names, rightDf._dtypesVector());
    } else {
      if (!isUndefined(opts.by)) {
        if (!isUndefined(opts.leftBy) || !isUndefined(opts.rightBy)) {
          throw new Error('cannot define opts.by, opts.leftBy, and ' +
            'opts.rightBy all together');
        }
        if (opts.by !== null && typeof opts.by === 'object' &&
          opts.by.type !== vectorProto.type) {
          // Extract left names from keys and right names from values
          var leftNames = Object.keys(opts.by);
          var rightNames = leftNames.map(function(key) {
            return opts.by[key];
          });
          leftKeyIdxVec =
            columnIndexing(leftNames, leftDf._names, leftDf._dtypesVector());
          rightKeyIdxVec =
            columnIndexing(rightNames, rightDf._names, rightDf._dtypesVector());
        } else {
          var keyNames = ensureVector(opts.by, 'string');
          leftKeyIdxVec =
            columnIndexing(keyNames, leftDf._names, leftDf._dtypesVector());
          rightKeyIdxVec =
            columnIndexing(keyNames, rightDf._names, rightDf._dtypesVector());
          if (keyNames.size() !== leftKeyIdxVec.size()) {
            throw new Error('duplicate names found for key columns');
          }
        }
      } else {
        if (isUndefined(opts.leftBy) || isUndefined(opts.rightBy)) {
          throw new Error('must specify both opts.leftBy and ' +
            'opts.rightBy together');
        }
        leftKeyIdxVec =
          columnIndexing(opts.leftBy, leftDf._names, leftDf._dtypesVector());
        rightKeyIdxVec =
          columnIndexing(opts.rightBy, rightDf._names, rightDf._dtypesVector());
      }
    }
  
    // Check validity of key column indices
    validateUniqueColInds(leftKeyIdxVec);
    validateUniqueColInds(rightKeyIdxVec);
    if (leftKeyIdxVec.size() !== rightKeyIdxVec.size()) {
      throw new Error('must select the same number of key columns on the ' +
        'left and right side; instead got ' + leftKeyIdxVec.size() +
        ', ' + rightKeyIdxVec.size());
    }
    if (leftKeyIdxVec.size() === 0 || rightKeyIdxVec.size() === 0) {
      throw new Error('must select at least one key column to join by');
    }
  
    return {
      left: leftKeyIdxVec,
      right: rightKeyIdxVec,
    };
  }