async function joinMonster(resolveInfo, context, dbCall, options = {}) {
  // we need to read the query AST and build a new "SQL AST" from which the SQL and
  const sqlAST = queryAST.queryASTToSqlAST(resolveInfo, options, context)
  const { sql, shapeDefinition } = await compileSqlAST(sqlAST, context, options)
  if (!sql) return {}

  // call their function for querying the DB, handle the different cases, do some validation, return a promise of the object
  let data = await handleUserDbCall(dbCall, sql, sqlAST, shapeDefinition)

  // if they are paginating, we'll get back an array which is essentially a "slice" of the whole data.
  // this function goes through the data tree and converts the arrays to Connection Objects
  data = arrToConnection(data, sqlAST)

  // so far we handled the first "batch". up until now, additional batches were ignored
  // this function recursively scanss the sqlAST and runs remaining batches
  await nextBatch(sqlAST, data, dbCall, context, options)

  // check for batch data
  if (Array.isArray(data)) {
    const childrenToCheck = sqlAST.children.filter(child => child.sqlBatch)
    return data.filter(d => {
      for (const child of childrenToCheck) {
        if (d[child.fieldName] == null) {
          return false
        }
      }
      return true
    })
  }

  return data
}