docs: function (type, table, expr, options, cb) {
              var args = argsToArray(arguments);
              if (isFunction(args[args.length - 1]) && !(args[args.length -1] instanceof Identifier)) {
                  cb = args[args.length - 1];
                  args.pop();
              } else {
                  cb = null;
              }
              type = args.shift(), table = args.shift(), expr = args.shift(), options = args.shift();
              expr = isUndefined(expr) ? null : expr, options = isUndefined(options) ? {} : options;
  
              var h;
              var usingJoin = Array.isArray(expr) && expr.length && expr.every(function (x) {
                  return isString(x) || isInstanceOf(x, Identifier);
              });
              if (usingJoin && !this.supportsJoinUsing) {
                  h = {};
                  expr.forEach(function (s) {
                      h[s] = s;
                  });
                  return this.joinTable(type, table, h, options);
              }
              var tableAlias, lastAlias;
              if (isHash(options)) {
                  tableAlias = options.tableAlias;
                  lastAlias = options.implicitQualifier;
              } else if (isString(options) || isInstanceOf(options, Identifier)) {
                  tableAlias = options;
                  lastAlias = null;
              } else {
                  throw new QueryError("Invalid options format for joinTable %j4", [options]);
              }
              var tableAliasNum, tableName;
              if (isInstanceOf(table, Dataset)) {
                  if (!tableAlias) {
                      tableAliasNum = (this.__opts.numDatasetSources || 0) + 1;
                      tableAlias = this._datasetAlias(tableAliasNum);
                  }
                  tableName = tableAlias;
              } else {
                  if (!isUndefined(table.tableName)) {
                      table = table.tableName;
                  }
                  if (Array.isArray(table)) {
                      table = table.map(this.stringToIdentifier, this);
                  } else {
                      table = isString(table) ? this.stringToIdentifier(table) : table;
                      var parts = this._splitAlias(table), implicitTableAlias = parts[1];
                      table = parts[0];
                      tableAlias = tableAlias || implicitTableAlias;
                      tableName = tableAlias || table;
                  }
              }
              var join;
              if (!expr && !cb) {
                  join = new JoinClause(type, table, tableAlias);
              } else if (usingJoin) {
                  if (cb) {
                      throw new QueryError("cant use a cb if an array is given");
                  }
                  join = new JoinUsingClause(expr, type, table, tableAlias);
              } else {
                  lastAlias = lastAlias || this.__opts["lastJoinedTable"] || this.firstSourceAlias;
                  if (Expression.isConditionSpecifier(expr)) {
                      var newExpr = [];
                      for (var i in expr) {
                          var val = expr[i];
                          if (Array.isArray(val) && val.length === 2) {
                              i = val[0], val = val[1];
                          }
                          var k = this.qualifiedColumnName(i, tableName), v;
                          if (isInstanceOf(val, Identifier)) {
                              v = val.qualify(lastAlias);
                          } else {
                              v = val;
                          }
                          newExpr.push([k, v]);
                      }
                      expr = newExpr;
                  }
                  if (isFunction(cb) && !(cb instanceof Identifier)) {
                      var expr2 = cb.apply(sql, [tableName, lastAlias, this.__opts.join || []]);
                      expr = expr ? new BooleanExpression("AND", expr, expr2) : expr2;
                  }
                  join = new JoinOnClause(expr, type, table, tableAlias);
              }
              var opts = {join: (this.__opts.join || []).concat([join]), lastJoinedTable: tableName};
              if (tableAliasNum) {
                  opts.numDatasetSources = tableAliasNum;
              }
              return this.mergeOptions(opts);
  
          }