All Downloads are FREE. Search and download functionalities are using the official Maven repository.

quasar.physical.mongodb.MongoDbPlanner.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014–2017 SlamData Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package quasar.physical.mongodb

import slamdata.Predef.{Map => _, _}
import quasar._, Planner._, Type.{Const => _, Coproduct => _, _}
import quasar.common.{PhaseResult, PhaseResults, PhaseResultT, PhaseResultTell, SortDir}
import quasar.connector.BackendModule
import quasar.contrib.matryoshka._
import quasar.contrib.pathy.{ADir, AFile}
import quasar.contrib.scalaz._, eitherT._
import quasar.ejson.EJson
import quasar.ejson.implicits._
import quasar.fp._
import quasar.fp.ski._
import quasar.fs.{FileSystemError, FileSystemErrT, MonadFsErr}, FileSystemError.qscriptPlanningFailed
import quasar.javascript._
import quasar.jscore, jscore.{JsCore, JsFn}
import quasar.physical.mongodb.WorkflowBuilder.{Subset => _, _}
import quasar.physical.mongodb.accumulator._
import quasar.physical.mongodb.expression._
import quasar.physical.mongodb.planner._
import quasar.physical.mongodb.planner.common._
import quasar.physical.mongodb.workflow.{ExcludeId => _, IncludeId => _, _}
import quasar.qscript._, RenderQScriptDSL._
import quasar.qscript.rewrites.{Coalesce => _, Optimize, PreferProjection, Rewrite}
import quasar.std.StdLib._ // TODO: remove this

import java.time.Instant
import matryoshka.{Hole => _, _}
import matryoshka.data._
import matryoshka.implicits._
import matryoshka.patterns._
import org.bson.BsonDocument
import scalaz._, Scalaz.{ToIdOps => _, _}

// TODO: This is generalizable to an arbitrary `Recursive` type, I think.
sealed abstract class InputFinder[T[_[_]]] {
  def apply[A](t: FreeMap[T]): FreeMap[T]
}

final case class Here[T[_[_]]]() extends InputFinder[T] {
  def apply[A](a: FreeMap[T]): FreeMap[T] = a
}

final case class There[T[_[_]]](index: Int, next: InputFinder[T])
    extends InputFinder[T] {
  def apply[A](a: FreeMap[T]): FreeMap[T] =
    a.resume.fold(fa => next(fa.toList.apply(index)), κ(a))
}

object MongoDbPlanner {
  import fixExprOp._

  type Partial[T[_[_]], In, Out] = (PartialFunction[List[In], Out], List[InputFinder[T]])

  type OutputM[A]      = PlannerError \/ A
  type ExecTimeR[F[_]] = MonadReader_[F, Instant]

  @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
  def generateTypeCheck[In, Out](or: (Out, Out) => Out)(f: PartialFunction[Type, In => Out]):
      Type => Option[In => Out] =
        typ => f.lift(typ).fold(
          typ match {
            case Type.Interval => generateTypeCheck(or)(f)(Type.Dec)
            case Type.Arr(_) => generateTypeCheck(or)(f)(Type.AnyArray)
            case Type.Timestamp
               | Type.Timestamp ⨿ Type.Date
               | Type.Timestamp ⨿ Type.Date ⨿ Type.Time =>
              generateTypeCheck(or)(f)(Type.Date)
            case Type.Timestamp ⨿ Type.Date ⨿ Type.Time ⨿ Type.Interval =>
              // Just repartition to match the right cases
              generateTypeCheck(or)(f)(Type.Interval ⨿ Type.Date)
            case Type.Int ⨿ Type.Dec ⨿ Type.Interval ⨿ Type.Str ⨿ (Type.Timestamp ⨿ Type.Date ⨿ Type.Time) ⨿ Type.Bool =>
              // Just repartition to match the right cases
              generateTypeCheck(or)(f)(
                Type.Int ⨿ Type.Dec ⨿ Type.Interval ⨿ Type.Str ⨿ (Type.Date ⨿ Type.Bool))
            case a ⨿ b =>
              (generateTypeCheck(or)(f)(a) ⊛ generateTypeCheck(or)(f)(b))(
                (a, b) => ((expr: In) => or(a(expr), b(expr))))
            case _ => None
          })(
          Some(_))

  def processMapFuncExpr
    [T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: ExecTimeR: MonadFsErr, EX[_]: Traverse, A]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX])
    (fm: FreeMapA[T, A])
    (recovery: A => Fix[ExprOp])
    (implicit inj: EX :<: ExprOp)
      : M[Fix[ExprOp]] = {

    val alg: AlgebraM[M, CoEnvMapA[T, A, ?], Fix[ExprOp]] =
      interpretM[M, MapFunc[T, ?], A, Fix[ExprOp]](
        recovery(_).point[M],
        expression(funcHandler))

    def convert(e: EX[FreeMapA[T, A]]): M[Fix[ExprOp]] =
      inj(e.map(_.cataM(alg))).sequence.map(_.embed)

    staticHandler.handle(fm).map(convert) getOrElse fm.cataM(alg)
  }

  def getSelector
    [T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr, EX[_]: Traverse]
    (fm: FreeMap[T], default: OutputM[PartialSelector[T]], galg: GAlgebra[(T[MapFunc[T, ?]], ?), MapFunc[T, ?], OutputM[PartialSelector[T]]])
    (implicit inj: EX :<: ExprOp)
      : OutputM[PartialSelector[T]] =
    fm.zygo(
      interpret[MapFunc[T, ?], Hole, T[MapFunc[T, ?]]](
        κ(MFC(MapFuncsCore.Undefined[T, T[MapFunc[T, ?]]]()).embed),
        _.embed),
      ginterpret[(T[MapFunc[T, ?]], ?), MapFunc[T, ?], Hole, OutputM[PartialSelector[T]]](
        κ(default), galg))

  def processMapFunc[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr: ExecTimeR, A]
    (fm: FreeMapA[T, A])(recovery: A => JsCore)
      : M[JsCore] =
    fm.cataM(interpretM[M, MapFunc[T, ?], A, JsCore](recovery(_).point[M], javascript))

  // FIXME: This is temporary. Should go away when the connector is complete.
  def unimplemented[M[_]: MonadFsErr, A](label: String): M[A] =
    raiseErr(qscriptPlanningFailed(InternalError.fromMsg(s"unimplemented $label")))

  // TODO: Should have a JsFn version of this for $reduce nodes.
  val accumulator: ReduceFunc[Fix[ExprOp]] => AccumOp[Fix[ExprOp]] = {
    import quasar.qscript.ReduceFuncs._

    {
      case Arbitrary(a)     => $first(a)
      case First(a)         => $first(a)
      case Last(a)          => $last(a)
      case Avg(a)           => $avg(a)
      case Count(_)         => $sum($literal(Bson.Int32(1)))
      case Max(a)           => $max(a)
      case Min(a)           => $min(a)
      case Sum(a)           => $sum(a)
      case UnshiftArray(a)  => $push(a)
      case UnshiftMap(k, v) => ???
    }
  }

  private def unpack[T[_[_]]: BirecursiveT, F[_]: Traverse](t: Free[F, T[F]]): T[F] =
    t.cata(interpret[F, T[F], T[F]](ι, _.embed))

  // NB: it's only safe to emit "core" expr ops here, but we always use the
  // largest type in WorkflowOp, so they're immediately injected into ExprOp.
  val check = new Check[Fix[ExprOp], ExprOp]

  def ejsonToExpression[M[_]: Applicative: MonadFsErr, EJ]
    (v: BsonVersion)(ej: EJ)(implicit EJ: Recursive.Aux[EJ, EJson])
      : M[Fix[ExprOp]] =
    ej.cataM(BsonCodec.fromEJson(v)).fold(pe => raiseErr(qscriptPlanningFailed(pe)), $literal(_).point[M])

  // TODO: Use `JsonCodec.encode` and avoid failing.
  def ejsonToJs[M[_]: Applicative: MonadFsErr, EJ: Show]
    (ej: EJ)(implicit EJ: Recursive.Aux[EJ, EJson])
      : M[JsCore] =
    ej.cata(Data.fromEJson).toJs.fold(
      raiseErr[M, JsCore](qscriptPlanningFailed(NonRepresentableEJson(ej.shows))))(
      _.point[M])

  def expression[
    T[_[_]]: RecursiveT: ShowT,
    M[_]: Monad: ExecTimeR: MonadFsErr,
    EX[_]: Traverse](funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?])(
    implicit inj: EX :<: ExprOp
  ): AlgebraM[M, MapFunc[T, ?], Fix[ExprOp]] = {

    import MapFuncsCore._
    import MapFuncsDerived._

    def handleCommon(mf: MapFunc[T, Fix[ExprOp]]): Option[Fix[ExprOp]] =
      funcHandler(mf).map(t => unpack(t.mapSuspension(inj)))

    def execTime(implicit ev: ExecTimeR[M]): M[Bson.Date] =
      OptionT[M, Bson.Date](ev.ask.map(Bson.Date.fromInstant(_)))
        .getOrElseF(raiseErr(
          qscriptPlanningFailed(InternalError.fromMsg("Could not get the current timestamp"))))

    val handleSpecialCore: MapFuncCore[T, Fix[ExprOp]] => M[Fix[ExprOp]] = {
      case Constant(v1) => unimplemented[M, Fix[ExprOp]]("Constant expression")
      case Now() => execTime map ($literal(_))
      case ToId(a1) => unimplemented[M, Fix[ExprOp]]("ToId expression")

      case Date(a1) => unimplemented[M, Fix[ExprOp]]("Date expression")
      case Time(a1) => unimplemented[M, Fix[ExprOp]]("Time expression")
      case Timestamp(a1) => unimplemented[M, Fix[ExprOp]]("Timestamp expression")
      case Interval(a1) => unimplemented[M, Fix[ExprOp]]("Interval expression")
      case StartOfDay(a1) => unimplemented[M, Fix[ExprOp]]("StartOfDay expression")
      case TemporalTrunc(a1, a2) => unimplemented[M, Fix[ExprOp]]("TemporalTrunc expression")

      case IfUndefined(a1, a2) => unimplemented[M, Fix[ExprOp]]("IfUndefined expression")

      case Within(a1, a2) => unimplemented[M, Fix[ExprOp]]("Within expression")

      case ExtractIsoYear(a1) =>
        unimplemented[M, Fix[ExprOp]]("ExtractIsoYear expression")
      case Integer(a1) => unimplemented[M, Fix[ExprOp]]("Integer expression")
      case Decimal(a1) => unimplemented[M, Fix[ExprOp]]("Decimal expression")
      // NB: The aggregation implementation of `ToString` does not handle ObjectId
      //     Here we force this case to be planned using JS
      case ToString($var(DocVar(_, Some(BsonField.Name("_id"))))) =>
        unimplemented[M, Fix[ExprOp]]("ToString _id expression")
      // FIXME: $substr is deprecated in Mongo 3.4. This implementation should be
      //        versioned along with the other functions in FuncHandler, taking into
      //        account the special case for ObjectId above. Mongo 3.4 should
      //        use $substrBytes instead of $substr
      case ToString(a1) => mkToString(a1, $substr).point[M]

      case MakeArray(a1) => unimplemented[M, Fix[ExprOp]]("MakeArray expression")
      case MakeMap(a1, a2) => unimplemented[M, Fix[ExprOp]]("MakeMap expression")
      case ConcatMaps(a1, a2) => unimplemented[M, Fix[ExprOp]]("ConcatMap expression")
      case ProjectKey($var(dv), $literal(Bson.Text(key))) =>
        $var(dv \ BsonField.Name(key)).point[M]
      case ProjectKey(el @ $arrayElemAt($var(dv), _), $literal(Bson.Text(key))) =>
        $let(ListMap(DocVar.Name("el") -> el),
          $var(DocVar.ROOT(BsonField.Name("$el")) \ BsonField.Name(key))).point[M]
      case ProjectKey(a1, a2) => unimplemented[M, Fix[ExprOp]](s"ProjectKey expression")
      case ProjectIndex(a1, a2)  => unimplemented[M, Fix[ExprOp]]("ProjectIndex expression")
      case DeleteKey(a1, a2)  => unimplemented[M, Fix[ExprOp]]("DeleteKey expression")

      // NB: Quasar strings are arrays of characters. However, MongoDB
      //     represent strings and arrays as distinct types. Moreoever, SQL^2
      //     exposes two functions: `array_length` to obtain the length of an
      //     array and `length` to obtain the length of a string. This
      //     distinction, however, is lost when LP is translated into
      //     QScript. There's only one `Length` MapFunc. The workaround here
      //     detects calls to array_length or length indirectly through the
      //     typechecks inserted around calls to `Length` or `ArrayLength` in
      //     LP typechecks.

      case Length(a1) => unimplemented[M, Fix[ExprOp]]("Length expression")
      case Guard(expr, Type.Str, cont @ $strLenCP(_), fallback) =>
        $cond(check.isString(expr), cont, fallback).point[M]
      case Guard(expr, Type.FlexArr(_, _, _), $strLenCP(str), fallback) =>
        $cond(check.isArray(expr), $size(str), fallback).point[M]

      // NB: This is maybe a NOP for Fix[ExprOp]s, as they (all?) safely
      //     short-circuit when given the wrong type. However, our guards may be
      //     more restrictive than the operation, in which case we still want to
      //     short-circuit, so …
      case Guard(expr, typ, cont, fallback) =>
        // NB: Even if certain checks aren’t needed by ExprOps, we have to
        //     maintain them because we may convert ExprOps to JS.
        //     Hopefully BlackShield will eliminate the need for this.
        @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
        def exprCheck: Type => Option[Fix[ExprOp] => Fix[ExprOp]] =
          generateTypeCheck[Fix[ExprOp], Fix[ExprOp]]($or(_, _)) {
            case Type.Null => check.isNull
            case Type.Int
               | Type.Dec
               | Type.Int ⨿ Type.Dec
               | Type.Int ⨿ Type.Dec ⨿ Type.Interval => check.isNumber
            case Type.Str => check.isString
            case Type.Obj(map, _) =>
              ((expr: Fix[ExprOp]) => {
                val basic = check.isObject(expr)
                expr match {
                  case $var(dv) =>
                    map.foldLeft(
                      basic)(
                      (acc, pair) =>
                      exprCheck(pair._2).fold(
                        acc)(
                        e => $and(acc, e($var(dv \ BsonField.Name(pair._1))))))
                  case _ => basic // FIXME: Check fields
                }
              })
            case Type.FlexArr(_, _, _) => check.isArray
            case Type.Binary => check.isBinary
            case Type.Id => check.isId
            case Type.Bool => check.isBoolean
            case Type.Date => check.isDate
            // NB: Some explicit coproducts for adjacent types.
            case Type.Int ⨿ Type.Dec ⨿ Type.Str => check.isNumberOrString
            case Type.Int ⨿ Type.Dec ⨿ Type.Interval ⨿ Type.Str => check.isNumberOrString
            case Type.Date ⨿ Type.Bool => check.isDateTimestampOrBoolean
            case Type.Syntaxed => check.isSyntaxed
          }
        exprCheck(typ).fold(cont)(f => $cond(f(expr), cont, fallback)).point[M]

      case Range(_, _)     => unimplemented[M, Fix[ExprOp]]("Range expression")
      case Search(_, _, _) => unimplemented[M, Fix[ExprOp]]("Search expression")
      case Split(_, _)     => unimplemented[M, Fix[ExprOp]]("Split expression")
    }

    val handleSpecialDerived: MapFuncDerived[T, Fix[ExprOp]] => M[Fix[ExprOp]] = {
      case Abs(a1) => unimplemented[M, Fix[ExprOp]]("Abs expression")
      case Ceil(a1) => unimplemented[M, Fix[ExprOp]]("Ceil expression")
      case Floor(a1) => unimplemented[M, Fix[ExprOp]]("Floor expression")
      case Trunc(a1) => unimplemented[M, Fix[ExprOp]]("Trunc expression")
      case Round(a1) => unimplemented[M, Fix[ExprOp]]("Round expression")
      case FloorScale(a1, a2) => unimplemented[M, Fix[ExprOp]]("FloorScale expression")
      case CeilScale(a1, a2) => unimplemented[M, Fix[ExprOp]]("CeilScale expression")
      case RoundScale(a1, a2) => unimplemented[M, Fix[ExprOp]]("RoundScale expression")
    }

    val handleSpecial: MapFunc[T, Fix[ExprOp]] => M[Fix[ExprOp]] = {
      case MFC(mfc) => handleSpecialCore(mfc)
      case MFD(mfd) => handleSpecialDerived(mfd)
    }

    mf => handleCommon(mf).cata(_.point[M], handleSpecial(mf))
  }

  def javascript[T[_[_]]: BirecursiveT: ShowT, M[_]: Applicative: MonadFsErr: ExecTimeR]
      : AlgebraM[M, MapFunc[T, ?], JsCore] = {
    import jscore.{
      Add => _, In => _,
      Lt => _, Lte => _, Gt => _, Gte => _, Eq => _, Neq => _,
      And => _, Or => _, Not => _,
      _}

    import MapFuncsCore._
    import MapFuncsDerived._

    val mjs = quasar.physical.mongodb.javascript[JsCore](_.embed)
    import mjs._

    // NB: Math.trunc is not present in MongoDB.
    def trunc(expr: JsCore): JsCore =
      Let(Name("x"), expr,
        BinOp(jscore.Sub,
          ident("x"),
          BinOp(jscore.Mod, ident("x"), Literal(Js.Num(1, false)))))

    def execTime(implicit ev: ExecTimeR[M]): M[JsCore] =
      ev.ask map (ts => Literal(Js.Str(ts.toString)))

    def handleCommon(mf: MapFunc[T, JsCore]): Option[JsCore] =
      JsFuncHandler.handle[MapFunc[T, ?]].apply(mf).map(unpack[Fix, JsCoreF])

    val handleSpecialCore: MapFuncCore[T, JsCore] => M[JsCore] = {
      case Constant(v1) => ejsonToJs[M, T[EJson]](v1)
      case JoinSideName(n) =>
        raiseErr[M, JsCore](qscriptPlanningFailed(UnexpectedJoinSide(n)))
      case Now() => execTime map (ts => New(Name("ISODate"), List(ts)))
      case Interval(a1) => unimplemented[M, JsCore]("Interval JS")

      // TODO: De-duplicate and move these to JsFuncHandler
      case ExtractCentury(date) =>
        Call(ident("NumberLong"), List(
          Call(Select(ident("Math"), "ceil"), List(
            BinOp(jscore.Div,
              Call(Select(date, "getUTCFullYear"), Nil),
              Literal(Js.Num(100, false))))))).point[M]
      case ExtractDayOfMonth(date) => Call(Select(date, "getUTCDate"), Nil).point[M]
      case ExtractDecade(date) =>
        Call(ident("NumberLong"), List(
          trunc(
            BinOp(jscore.Div,
              Call(Select(date, "getUTCFullYear"), Nil),
              Literal(Js.Num(10, false)))))).point[M]
      case ExtractDayOfWeek(date) =>
        Call(Select(date, "getUTCDay"), Nil).point[M]
      case ExtractDayOfYear(date) =>
        Call(ident("NumberInt"), List(
          Call(Select(ident("Math"), "floor"), List(
            BinOp(jscore.Add,
              BinOp(jscore.Div,
                BinOp(Sub,
                  date,
                  New(Name("Date"), List(
                    Call(Select(date, "getFullYear"), Nil),
                    Literal(Js.Num(0, false)),
                    Literal(Js.Num(0, false))))),
                Literal(Js.Num(86400000, false))),
              Literal(Js.Num(1, false))))))).point[M]
      case ExtractEpoch(date) =>
        Call(ident("NumberLong"), List(
          BinOp(jscore.Div,
            Call(Select(date, "valueOf"), Nil),
            Literal(Js.Num(1000, false))))).point[M]
      case ExtractHour(date) => Call(Select(date, "getUTCHours"), Nil).point[M]
      case ExtractIsoDayOfWeek(date) =>
        Let(Name("x"), Call(Select(date, "getUTCDay"), Nil),
          If(
            BinOp(jscore.Eq, ident("x"), Literal(Js.Num(0, false))),
            Literal(Js.Num(7, false)),
            ident("x"))).point[M]
      case ExtractIsoYear(date) =>
        Call(Select(date, "getUTCFullYear"), Nil).point[M]
      case ExtractMicroseconds(date) =>
        BinOp(jscore.Mult,
          BinOp(jscore.Add,
            Call(Select(date, "getUTCMilliseconds"), Nil),
            BinOp(jscore.Mult,
              Call(Select(date, "getUTCSeconds"), Nil),
              Literal(Js.Num(1000, false)))),
          Literal(Js.Num(1000, false))).point[M]
      case ExtractMillennium(date) =>
        Call(ident("NumberLong"), List(
          Call(Select(ident("Math"), "ceil"), List(
            BinOp(jscore.Div,
              Call(Select(date, "getUTCFullYear"), Nil),
              Literal(Js.Num(1000, false))))))).point[M]
      case ExtractMilliseconds(date) =>
        BinOp(jscore.Add,
          Call(Select(date, "getUTCMilliseconds"), Nil),
          BinOp(jscore.Mult,
            Call(Select(date, "getUTCSeconds"), Nil),
            Literal(Js.Num(1000, false)))).point[M]
      case ExtractMinute(date) =>
        Call(Select(date, "getUTCMinutes"), Nil).point[M]
      case ExtractMonth(date) =>
        BinOp(jscore.Add,
          Call(Select(date, "getUTCMonth"), Nil),
          Literal(Js.Num(1, false))).point[M]
      case ExtractQuarter(date) =>
        Call(ident("NumberInt"), List(
          BinOp(jscore.Add,
            BinOp(jscore.BitOr,
              BinOp(jscore.Div,
                Call(Select(date, "getUTCMonth"), Nil),
                Literal(Js.Num(3, false))),
              Literal(Js.Num(0, false))),
            Literal(Js.Num(1, false))))).point[M]
      case ExtractSecond(date) =>
        BinOp(jscore.Add,
          Call(Select(date, "getUTCSeconds"), Nil),
          BinOp(jscore.Div,
            Call(Select(date, "getUTCMilliseconds"), Nil),
            Literal(Js.Num(1000, false)))).point[M]
      case ExtractWeek(date) =>
        Call(ident("NumberInt"), List(
          Call(Select(ident("Math"), "floor"), List(
            BinOp(jscore.Add,
              BinOp(jscore.Div,
                Let(Name("startOfYear"),
                  New(Name("Date"), List(
                    Call(Select(date, "getFullYear"), Nil),
                    Literal(Js.Num(0, false)),
                    Literal(Js.Num(1, false)))),
                  BinOp(jscore.Add,
                    BinOp(Div,
                      BinOp(Sub, date, ident("startOfYear")),
                      Literal(Js.Num(86400000, false))),
                    BinOp(jscore.Add,
                      Call(Select(ident("startOfYear"), "getDay"), Nil),
                      Literal(Js.Num(1, false))))),
                Literal(Js.Num(7, false))),
              Literal(Js.Num(1, false))))))).point[M]

      case MakeMap(Embed(LiteralF(Js.Str(str))), a2) => Obj(ListMap(Name(str) -> a2)).point[M]
      // TODO: pull out the literal, and handle this case in other situations
      case MakeMap(a1, a2) => Obj(ListMap(Name("__Quasar_non_string_map") ->
       Arr(List(Arr(List(a1, a2)))))).point[M]
      case ConcatArrays(Embed(ArrF(a1)), Embed(ArrF(a2))) =>
        Arr(a1 |+| a2).point[M]
      case ConcatArrays(a1, a2) =>
        If(BinOp(jscore.Or, isArray(a1), isArray(a2)),
          Call(Select(a1, "concat"), List(a2)),
          BinOp(jscore.Add, a1, a2)).point[M]
      case ConcatMaps(Embed(ObjF(o1)), Embed(ObjF(o2))) =>
        Obj(o1 ++ o2).point[M]
      case ConcatMaps(a1, a2) => SpliceObjects(List(a1, a2)).point[M]

      case Guard(expr, typ, cont, fallback) =>
        val jsCheck: Type => Option[JsCore => JsCore] =
          generateTypeCheck[JsCore, JsCore](BinOp(jscore.Or, _, _)) {
            case Type.Null             => isNull
            case Type.Dec              => isDec
            case Type.Int
               | Type.Int ⨿ Type.Dec
               | Type.Int ⨿ Type.Dec ⨿ Type.Interval
                => isAnyNumber
            case Type.Str              => isString
            case Type.Obj(_, _) ⨿ Type.FlexArr(_, _, _)
                => isObjectOrArray
            case Type.Obj(_, _)        => isObject
            case Type.FlexArr(_, _, _) => isArray
            case Type.Binary           => isBinary
            case Type.Id               => isObjectId
            case Type.Bool             => isBoolean
            case Type.Date             => isDate
            case Type.Syntaxed         => isSyntaxed
          }
        jsCheck(typ).fold[M[JsCore]](
          raiseErr(qscriptPlanningFailed(InternalError.fromMsg("uncheckable type"))))(
          f => If(f(expr), cont, fallback).point[M])
      // TODO: Specify the function name for pattern match failures
      case _ => unimplemented[M, JsCore]("JS function")
    }

    val handleSpecialDerived: MapFuncDerived[T, JsCore] => M[JsCore] = {
      case Abs(a1)   => unimplemented[M, JsCore]("Abs JS")
      case Ceil(a1)  => unimplemented[M, JsCore]("Ceil JS")
      case Floor(a1) => unimplemented[M, JsCore]("Floor JS")
      case Trunc(a1) => unimplemented[M, JsCore]("Trunc JS")
      case Round(a1) => unimplemented[M, JsCore]("Round JS")
      case FloorScale(a1, a2) => unimplemented[M, JsCore]("FloorScale JS")
      case CeilScale(a1, a2) => unimplemented[M, JsCore]("CeilScale JS")
      case RoundScale(a1, a2) => unimplemented[M, JsCore]("RoundScale JS")
    }

    val handleSpecial: MapFunc[T, JsCore] => M[JsCore] = {
      case MFC(mfc) => handleSpecialCore(mfc)
      case MFD(mfd) => handleSpecialDerived(mfd)
    }

    mf => handleCommon(mf).cata(_.point[M], handleSpecial(mf))
  }

  // TODO: Need this until the old connector goes away and we can redefine
  //       `Selector` as `Selector[A, B]`, where `A` is the field type
  //       (naturally `BsonField`), and `B` is the recursive parameter.
  type PartialSelector[T[_[_]]] = Partial[T, BsonField, Selector]

  def defaultSelector[T[_[_]]]: PartialSelector[T] = (
    { case List(field) =>
      Selector.Doc(ListMap(
        field -> Selector.Expr(Selector.Eq(Bson.Bool(true)))))
    },
    List(Here[T]()))

  def invoke2Nel[T[_[_]]](x: OutputM[PartialSelector[T]], y: OutputM[PartialSelector[T]])(f: (Selector, Selector) => Selector):
      OutputM[PartialSelector[T]] =
    (x ⊛ y) { case ((f1, p1), (f2, p2)) =>
      ({ case list =>
        f(f1(list.take(p1.size)), f2(list.drop(p1.size)))
      },
        p1.map(There(0, _)) ++ p2.map(There(1, _)))
    }

  def typeSelector[T[_[_]]: RecursiveT: ShowT]:
      GAlgebra[(T[MapFunc[T, ?]], ?), MapFunc[T, ?], OutputM[PartialSelector[T]]] = { node =>

    import MapFuncsCore._

    def invoke2Rel[T[_[_]]](x: OutputM[PartialSelector[T]], y: OutputM[PartialSelector[T]])(f: (Selector, Selector) => Selector):
        OutputM[PartialSelector[T]] =
      (x.toOption, y.toOption) match {
        case (Some((f1, p1)), Some((f2, p2)))=>
          invoke2Nel(x, y)(f)
        case (Some((f1, p1)), None) =>
          (f1, p1.map(There(0, _))).right
        case (None, Some((f2, p2))) =>
          (f2, p2.map(There(1, _))).right
        case _ => InternalError.fromMsg(node.map(_._1).shows).left
      }

    node match {
      // NB: the pick of Selector for these two cases determine how restrictive the
      //     extracted typechecks are. See #2883 for more details
      case MFC(And(a, b)) => invoke2Rel(a._2, b._2)(Selector.And(_, _))
      case MFC(Or(a, b))  => invoke2Rel(a._2, b._2)(Selector.Or(_, _))

      // NB: we want to extract typechecks from both sides of a comparison operator
      //     Typechecks extracted from both sides are ANDed. Similarly to the `And`
      //     and `Or` case above, the selector choice can be tweaked depending on how
      //     strict we want to be with extracted typechecks. See #2883
      case MFC(Eq(a, b))  => invoke2Rel(a._2, b._2)(Selector.And(_, _))
      case MFC(Neq(a, b)) => invoke2Rel(a._2, b._2)(Selector.And(_, _))
      case MFC(Lt(a, b))  => invoke2Rel(a._2, b._2)(Selector.And(_, _))
      case MFC(Lte(a, b)) => invoke2Rel(a._2, b._2)(Selector.And(_, _))
      case MFC(Gt(a, b))  => invoke2Rel(a._2, b._2)(Selector.And(_, _))
      case MFC(Gte(a, b)) => invoke2Rel(a._2, b._2)(Selector.And(_, _))

      // NB: Undefined() is Hole in disguise here. We don't have a way to directly represent
      //     a FreeMap's leaves with this fixpoint, so we use Undefined() instead.
      case MFC(Guard((Embed(MFC(ProjectKey(Embed(MFC(Undefined())), _))), _), typ, cont, _)) =>
        def selCheck: Type => Option[BsonField => Selector] =
          generateTypeCheck[BsonField, Selector](Selector.Or(_, _)) {
            case Type.Null => ((f: BsonField) =>  Selector.Doc(f -> Selector.Type(BsonType.Null)))
            case Type.Dec => ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.Dec)))
            case Type.Int =>
              ((f: BsonField) => Selector.Or(
                Selector.Doc(f -> Selector.Type(BsonType.Int32)),
                Selector.Doc(f -> Selector.Type(BsonType.Int64))))
            case Type.Int ⨿ Type.Dec ⨿ Type.Interval =>
              ((f: BsonField) =>
                Selector.Or(
                  Selector.Doc(f -> Selector.Type(BsonType.Int32)),
                  Selector.Doc(f -> Selector.Type(BsonType.Int64)),
                  Selector.Doc(f -> Selector.Type(BsonType.Dec))))
            case Type.Str => ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.Text)))
            case Type.Obj(_, _) =>
              ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.Doc)))

            // NB: Selector.Type(BsonType.Arr) will not match arrays, instead we use the suggestion in Mongo docs
            // See: https://docs.mongodb.com/manual/reference/operator/query/type/#document-querying-by-array-type
            case Type.FlexArr(_, _, _) =>
              ((f: BsonField) => Selector.Doc(f -> Selector.ElemMatch(Selector.Exists(true).right)))
            case Type.Binary =>
              ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.Binary)))
            case Type.Id =>
              ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.ObjectId)))
            case Type.Bool => ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.Bool)))
            case Type.Date =>
              ((f: BsonField) => Selector.Doc(f -> Selector.Type(BsonType.Date)))
          }
        selCheck(typ).fold[OutputM[PartialSelector[T]]](
          -\/(InternalError.fromMsg(node.map(_._1).shows)))(
          f =>
          \/-(cont._2.fold[PartialSelector[T]](
            κ(({ case List(field) => f(field) }, List(There(0, Here[T]())))),
            { case (f2, p2) => ({ case head :: tail => Selector.And(f(head), f2(tail)) }, There(0, Here[T]()) :: p2.map(There(1, _)))
            })))

      case _ => -\/(InternalError fromMsg node.map(_._1).shows)
    }
  }


  /** The selector phase tries to turn expressions into MongoDB selectors – i.e.
    * Mongo query expressions. Selectors are only used for the filtering
    * pipeline op, so it's quite possible we build more stuff than is needed
    * (but it doesn’t matter, unneeded annotations will be ignored by the
    * pipeline phase).
    *
    * Like the expression op phase, this one requires bson field annotations.
    *
    * Most expressions cannot be turned into selector expressions without using
    * the "\$where" operator, which allows embedding JavaScript
    * code. Unfortunately, using this operator turns filtering into a full table
    * scan. We should do a pass over the tree to identify partial boolean
    * expressions which can be turned into selectors, factoring out the
    * leftovers for conversion using \$where.
    */
  def selector[T[_[_]]: RecursiveT: ShowT](v: BsonVersion):
      GAlgebra[(T[MapFunc[T, ?]], ?), MapFunc[T, ?], OutputM[PartialSelector[T]]] = { node =>
    import MapFuncsCore._

    type Output = OutputM[PartialSelector[T]]

    object IsBson {
      def unapply(x: (T[MapFunc[T, ?]], Output)): Option[Bson] =
        x._1.project match {
          case MFC(Constant(b)) => b.cataM(BsonCodec.fromEJson(v)).toOption
          case _ => None
        }
    }

    object IsBool {
      def unapply(v: (T[MapFunc[T, ?]], Output)): Option[Boolean] =
        v match {
          case IsBson(Bson.Bool(b)) => b.some
          case _                    => None
        }
    }

    object IsText {
      def unapply(v: (T[MapFunc[T, ?]], Output)): Option[String] =
        v match {
          case IsBson(Bson.Text(str)) => Some(str)
          case _                      => None
        }
    }

    object IsDate {
      def unapply(v: (T[MapFunc[T, ?]], Output)): Option[Data.Date] =
        v._1.project match {
          case MFC(Constant(d @ Data.Date(_))) => Some(d)
          case _                               => None
        }
    }

    val relFunc: MapFunc[T, _] => Option[Bson => Selector.Condition] = {
      case MFC(Eq(_, _))  => Some(Selector.Eq)
      case MFC(Neq(_, _)) => Some(Selector.Neq)
      case MFC(Lt(_, _))  => Some(Selector.Lt)
      case MFC(Lte(_, _)) => Some(Selector.Lte)
      case MFC(Gt(_, _))  => Some(Selector.Gt)
      case MFC(Gte(_, _)) => Some(Selector.Gte)
      case _              => None
    }

    val default: PartialSelector[T] = defaultSelector[T]

    def invoke(func: MapFunc[T, (T[MapFunc[T, ?]], Output)]): Output = {
      /**
        * All the relational operators require a field as one parameter, and
        * BSON literal value as the other parameter. So we have to try to
        * extract out both a field annotation and a selector and then verify
        * the selector is actually a BSON literal value before we can
        * construct the relational operator selector. If this fails for any
        * reason, it just means the given expression cannot be represented
        * using MongoDB's query operators, and must instead be written as
        * Javascript using the "$where" operator.
        */
      def relop
        (x: (T[MapFunc[T, ?]], Output), y: (T[MapFunc[T, ?]], Output))
        (f: Bson => Selector.Condition, r: Bson => Selector.Condition):
          Output =
        (x, y) match {
          case (_, IsBson(v2)) =>
            \/-(({ case List(f1) => Selector.Doc(ListMap(f1 -> Selector.Expr(f(v2)))) }, List(There(0, Here[T]()))))
          case (IsBson(v1), _) =>
            \/-(({ case List(f2) => Selector.Doc(ListMap(f2 -> Selector.Expr(r(v1)))) }, List(There(1, Here[T]()))))

          case (_, _) => -\/(InternalError fromMsg node.map(_._1).shows)
        }

      def relDateOp1(f: Bson.Date => Selector.Condition, date: Data.Date, g: Data.Date => Data.Timestamp, index: Int): Output =
        Bson.Date.fromInstant(g(date).value).fold[Output](
          -\/(NonRepresentableData(g(date))))(
          d => \/-((
            { case x :: Nil => Selector.Doc(x -> f(d)) },
            List(There(index, Here[T]())))))

      def relDateOp2(conj: (Selector, Selector) => Selector, f1: Bson.Date => Selector.Condition, f2: Bson.Date => Selector.Condition, date: Data.Date, g1: Data.Date => Data.Timestamp, g2: Data.Date => Data.Timestamp, index: Int): Output =
        ((Bson.Date.fromInstant(g1(date).value) \/> NonRepresentableData(g1(date))) ⊛
          (Bson.Date.fromInstant(g2(date).value) \/> NonRepresentableData(g2(date))))((d1, d2) =>
          (
            { case x :: Nil =>
              conj(
                Selector.Doc(x -> f1(d1)),
                Selector.Doc(x -> f2(d2)))
            },
            List(There(index, Here[T]()))))

      val flipCore: MapFuncCore[T, _] => Option[MapFuncCore[T, _]] = {
        case Eq(a, b)  => Some(Eq(a, b))
        case Neq(a, b) => Some(Neq(a, b))
        case Lt(a, b)  => Some(Gt(a, b))
        case Lte(a, b) => Some(Gte(a, b))
        case Gt(a, b)  => Some(Lt(a, b))
        case Gte(a, b) => Some(Lte(a, b))
        case And(a, b) => Some(And(a, b))
        case Or(a, b)  => Some(Or(a, b))
        case _         => None
      }

      val flip: MapFunc[T, _] => Option[MapFunc[T, _]] = {
        case MFC(mfc) => flipCore(mfc).map(MFC(_))
        case _ => None
      }

      def reversibleRelop(x: (T[MapFunc[T, ?]], Output), y: (T[MapFunc[T, ?]], Output))(f: MapFunc[T, _]): Output =
        (relFunc(f) ⊛ flip(f).flatMap(relFunc))(relop(x, y)(_, _)).getOrElse(-\/(InternalError fromMsg "couldn’t decipher operation"))

      func match {
        case MFC(Constant(_)) => \/-(default)
        case MFC(And(a, b))   => invoke2Nel(a._2, b._2)(Selector.And.apply _)
        case MFC(Or(a, b))    => invoke2Nel(a._2, b._2)(Selector.Or.apply _)

        case MFC(Gt(_, IsDate(d2)))  => relDateOp1(Selector.Gte, d2, date.startOfNextDay, 0)
        case MFC(Lt(IsDate(d1), _))  => relDateOp1(Selector.Gte, d1, date.startOfNextDay, 1)

        case MFC(Lt(_, IsDate(d2)))  => relDateOp1(Selector.Lt,  d2, date.startOfDay, 0)
        case MFC(Gt(IsDate(d1), _))  => relDateOp1(Selector.Lt,  d1, date.startOfDay, 1)

        case MFC(Gte(_, IsDate(d2))) => relDateOp1(Selector.Gte, d2, date.startOfDay, 0)
        case MFC(Lte(IsDate(d1), _)) => relDateOp1(Selector.Gte, d1, date.startOfDay, 1)

        case MFC(Lte(_, IsDate(d2))) => relDateOp1(Selector.Lt,  d2, date.startOfNextDay, 0)
        case MFC(Gte(IsDate(d1), _)) => relDateOp1(Selector.Lt,  d1, date.startOfNextDay, 1)

        case MFC(Eq(_, IsDate(d2))) => relDateOp2(Selector.And(_, _), Selector.Gte, Selector.Lt, d2, date.startOfDay, date.startOfNextDay, 0)
        case MFC(Eq(IsDate(d1), _)) => relDateOp2(Selector.And(_, _), Selector.Gte, Selector.Lt, d1, date.startOfDay, date.startOfNextDay, 1)

        case MFC(Neq(_, IsDate(d2))) => relDateOp2(Selector.Or(_, _), Selector.Lt, Selector.Gte, d2, date.startOfDay, date.startOfNextDay, 0)
        case MFC(Neq(IsDate(d1), _)) => relDateOp2(Selector.Or(_, _), Selector.Lt, Selector.Gte, d1, date.startOfDay, date.startOfNextDay, 1)

        case MFC(Eq(a, b))  => reversibleRelop(a, b)(func)
        case MFC(Neq(a, b)) => reversibleRelop(a, b)(func)
        case MFC(Lt(a, b))  => reversibleRelop(a, b)(func)
        case MFC(Lte(a, b)) => reversibleRelop(a, b)(func)
        case MFC(Gt(a, b))  => reversibleRelop(a, b)(func)
        case MFC(Gte(a, b)) => reversibleRelop(a, b)(func)

        // NB: workaround patmat exhaustiveness checker bug. Merge with previous `match`
        //     once solved.
        case x => x match {
          case MFC(Within(a, b)) =>
            relop(a, b)(
              Selector.In.apply _,
              x => Selector.ElemMatch(\/-(Selector.In(Bson.Arr(List(x))))))

          case MFC(Search(_, IsText(patt), IsBool(b))) =>
            \/-(({ case List(f1) =>
              Selector.Doc(ListMap(f1 -> Selector.Expr(Selector.Regex(patt, b, true, false, false)))) },
              List(There(0, Here[T]()))))

          case MFC(Between(_, IsBson(lower), IsBson(upper))) =>
            \/-(({ case List(f) => Selector.And(
              Selector.Doc(f -> Selector.Gte(lower)),
              Selector.Doc(f -> Selector.Lte(upper)))
            },
              List(There(0, Here[T]()))))

          case MFC(Not((_, v))) =>
            v.map { case (sel, inputs) => (sel andThen (_.negate), inputs.map(There(0, _))) }

          case MFC(Cond((_, v), _, _)) =>
            v.map { case (sel, inputs) => (sel, inputs.map(There(0, _))) }

          case MFC(ProjectKey((Embed(MFC(Cond(_, _, _))), v), _)) =>
            v.map { case (sel, inputs) => (sel, inputs.map(There(0, _))) }

          case MFC(Guard(_, typ, (_, cont), (Embed(MFC(Undefined())), _))) =>
            cont.map { case (sel, inputs) => (sel, inputs.map(There(1, _))) }
          case MFC(Guard(_, typ, (_, cont), (Embed(MFC(MakeArray(Embed(MFC(Undefined()))))), _))) =>
            cont.map { case (sel, inputs) => (sel, inputs.map(There(1, _))) }

          case _ => -\/(InternalError fromMsg node.map(_._1).shows)
        }
      }
    }

    invoke(node)
  }

  /** Brings a [[WBM]] into our `M`. */
  def liftM[M[_]: Monad: MonadFsErr, A](meh: WBM[A]): M[A] =
    meh.fold(
      e => raiseErr(qscriptPlanningFailed(e)),
      _.point[M])

  def createFieldName(prefix: String, i: Int): String = prefix + i.toString

  trait Planner[F[_]] {
    type IT[G[_]]

    def plan
      [M[_]: Monad: ExecTimeR: MonadFsErr, WF[_]: Functor: Coalesce: Crush, EX[_]: Traverse]
      (cfg: PlannerConfig[IT, EX, WF])
      (implicit
        ev0: WorkflowOpCoreF :<: WF,
        ev1: RenderTree[WorkflowBuilder[WF]],
        ev2: WorkflowBuilder.Ops[WF],
        ev3: EX :<: ExprOp):
        AlgebraM[M, F, WorkflowBuilder[WF]]
  }

  object Planner {
    type Aux[T[_[_]], F[_]] = Planner[F] { type IT[G[_]] = T[G] }

    def apply[T[_[_]], F[_]](implicit ev: Planner.Aux[T, F]) = ev

    implicit def shiftedReadFile[T[_[_]]: BirecursiveT: ShowT]: Planner.Aux[T, Const[ShiftedRead[AFile], ?]] =
      new Planner[Const[ShiftedRead[AFile], ?]] {
        type IT[G[_]] = T[G]
        def plan
          [M[_]: Monad: ExecTimeR: MonadFsErr, WF[_]: Functor: Coalesce: Crush, EX[_]: Traverse]
          (cfg: PlannerConfig[T, EX, WF])
          (implicit
            ev0: WorkflowOpCoreF :<: WF,
            ev1: RenderTree[WorkflowBuilder[WF]],
            WB: WorkflowBuilder.Ops[WF],
            ev3: EX :<: ExprOp) =
          qs => Collection
            .fromFile(qs.getConst.path)
            .fold(
              e => raiseErr(qscriptPlanningFailed(PlanPathError(e))),
              coll => {
                val dataset = WB.read(coll)
                // TODO: exclude `_id` from the value here?
                qs.getConst.idStatus match {
                  case IdOnly    =>
                    getExprBuilder[T, M, WF, EX](
                      cfg.funcHandler, cfg.staticHandler)(
                      dataset,
                        Free.roll(MFC(MapFuncsCore.ProjectKey[T, FreeMap[T]](HoleF[T], MapFuncsCore.StrLit("_id")))))
                  case IncludeId =>
                    getExprBuilder[T, M, WF, EX](
                      cfg.funcHandler, cfg.staticHandler)(
                      dataset,
                        MapFuncCore.StaticArray(List(
                          Free.roll(MFC(MapFuncsCore.ProjectKey[T, FreeMap[T]](HoleF[T], MapFuncsCore.StrLit("_id")))),
                          HoleF)))
                  case ExcludeId => dataset.point[M]
                }
              })
      }

    implicit def qscriptCore[T[_[_]]: BirecursiveT: EqualT: ShowT]:
        Planner.Aux[T, QScriptCore[T, ?]] =
      new Planner[QScriptCore[T, ?]] {
        import MapFuncsCore._
        import MapFuncCore._

        type IT[G[_]] = T[G]

        @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
        def plan
          [M[_]: Monad: ExecTimeR: MonadFsErr,
            WF[_]: Functor: Coalesce: Crush,
            EX[_]: Traverse]
          (cfg: PlannerConfig[T, EX, WF])
          (implicit
            ev0: WorkflowOpCoreF :<: WF,
            ev1: RenderTree[WorkflowBuilder[WF]],
            WB: WorkflowBuilder.Ops[WF],
            ev3: EX :<: ExprOp) = {
          case qscript.Map(src, f) =>
            getExprBuilder[T, M, WF, EX](cfg.funcHandler, cfg.staticHandler)(src, f)
          case LeftShift(src, struct, id, shiftType, repair) => {
            def rewriteUndefined[A]: CoMapFuncR[T, A] => Option[CoMapFuncR[T, A]] = {
              case CoEnv(\/-(MFC(Guard(exp, tpe @ Type.FlexArr(_, _, _), exp0, Embed(CoEnv(\/-(MFC(Undefined())))))))) =>
                rollMF[T, A](MFC(Guard(exp, tpe, exp0, Free.roll(MFC(MakeArray(Free.roll(MFC(Undefined())))))))).some
              case _ => none
            }

            // FIXME: Remove the `Cond`s extracted by the selector phase, not every `Cond(_, _, Undefined)` as here.
            def elideCond[A]: CoMapFuncR[T, A] => Option[CoMapFuncR[T, A]] = {
              case CoEnv(\/-(MFC(Cond(if_, then_, Embed(CoEnv(\/-(MFC(Undefined())))))))) =>
                CoEnv(then_.resume.swap).some
              case _ => none
            }

            def filterBuilder(src: WorkflowBuilder[WF], partialSel: PartialSelector[T]):
                M[WorkflowBuilder[WF]] = {
              val (sel, inputs) = partialSel

              inputs.traverse(f => handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, f(struct)))
                .map(WB.filter(src, _, sel))
            }

            def transform[A]: CoMapFuncR[T, A] => Option[CoMapFuncR[T, A]] =
              applyTransforms(elideCond[A], rewriteUndefined[A])

            val selectors = getSelector[T, M, EX](
              struct, InternalError.fromMsg("Not a selector").left, selector[T](cfg.bsonVersion))

            if (repair.contains(LeftSideF)) {
              val exprMerge: JoinFunc[T] => M[Fix[ExprOp]] =
                getExprMerge[T, M, EX](cfg.funcHandler, cfg.staticHandler)(_, DocField(BsonField.Name("s")), DocField(BsonField.Name("f")))
              val jsMerge: JoinFunc[T] => M[JsFn] =
                getJsMerge[T, M](_, jscore.Select(jscore.Ident(JsFn.defaultName), "s"), jscore.Select(jscore.Ident(JsFn.defaultName), "f"))

              shiftType match {
                case ShiftType.Array => {
                  selectors.fold(_ => handleFreeMap[T, M, EX](
                    cfg.funcHandler,
                    cfg.staticHandler,
                    struct.transCata[FreeMap[T]](orOriginal(rewriteUndefined[Hole]))) >>= (target =>
                    getBuilder[T, M, WF, EX, JoinSide](exprOrJs(_)(exprMerge, jsMerge))(
                      FlatteningBuilder(
                        DocBuilder(
                          src,
                          ListMap(
                            BsonField.Name("s") -> docVarToExpr(DocVar.ROOT()),
                            BsonField.Name("f") -> target)),
                        Set(StructureType.Array(DocField(BsonField.Name("f")), id))),
                      repair.transCata[JoinFunc[T]](orOriginal(rewriteUndefined[JoinSide])))), { sel =>
                    val struct0 =
                      handleFreeMap[T, M, EX](
                        cfg.funcHandler,
                        cfg.staticHandler,
                        struct.transCata[FreeMap[T]](orOriginal(transform[Hole])))
                    val repair0 = repair.transCata[JoinFunc[T]](orOriginal(transform[JoinSide]))

                    (struct0 ⊛ filterBuilder(src, sel))((struct1, src0) =>
                      getBuilder[T, M, WF, EX, JoinSide](exprOrJs(_)(exprMerge, jsMerge))(
                        FlatteningBuilder(
                          DocBuilder(
                            src0,
                            ListMap(
                              BsonField.Name("s") -> docVarToExpr(DocVar.ROOT()),
                              BsonField.Name("f") -> struct1)),
                          Set(StructureType.Array(DocField(BsonField.Name("f")), id))),
                        repair0)).join
                  })
                }
                case _ =>
                  (handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, struct) ⊛
                    getJsMerge[T, M](
                      repair,
                      jscore.Select(jscore.Ident(JsFn.defaultName), "s"),
                      jscore.Select(jscore.Ident(JsFn.defaultName), "f")))((expr, j) =>
                    ExprBuilder(
                      FlatteningBuilder(
                        DocBuilder(
                          src,
                          ListMap(
                            BsonField.Name("s") -> docVarToExpr(DocVar.ROOT()),
                            BsonField.Name("f") -> expr)),
                        // TODO: Handle arrays properly
                        Set(StructureType.Object(DocField(BsonField.Name("f")), id))),
                      -\&/(j)))
              }
            }
            else
              shiftType match {
                case ShiftType.Array => {
                  val struct0 = struct.transCata[FreeMap[T]](orOriginal(rewriteUndefined[Hole]))
                  val repair0 = repair.as[Hole](SrcHole).transCata[FreeMap[T]](orOriginal(rewriteUndefined[Hole]))

                  getExprBuilder[T, M, WF, EX](cfg.funcHandler, cfg.staticHandler)(src, struct0) >>= (builder =>
                    getExprBuilder[T, M, WF, EX](
                      cfg.funcHandler, cfg.staticHandler)(
                      FlatteningBuilder(
                        builder,
                        Set(StructureType.Array(DocVar.ROOT(), id))),
                        repair0))
                }
                case _ =>
                  getExprBuilder[T, M, WF, EX](cfg.funcHandler, cfg.staticHandler)(src, struct) >>= (builder =>
                    getExprBuilder[T, M, WF, EX](
                      cfg.funcHandler, cfg.staticHandler)(
                      FlatteningBuilder(
                        builder,
                        Set(StructureType.Object(DocVar.ROOT(), id))),
                        repair.as(SrcHole)))
              }

          }
          case Reduce(src, bucket, reducers, repair) =>
            (bucket.traverse(handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, _)) ⊛
              reducers.traverse(_.traverse(handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, _))))((b, red) => {
                getReduceBuilder[T, M, WF, EX](
                  cfg.funcHandler, cfg.staticHandler)(
                  // TODO: This work should probably be done in `toWorkflow`.
                  semiAlignExpr[λ[α => List[ReduceFunc[α]]]](red)(Traverse[List].compose).fold(
                    WB.groupBy(
                      DocBuilder(
                        src,
                        // FIXME: Doesn’t work with UnshiftMap
                        red.unite.zipWithIndex.map(_.map(i => BsonField.Name(createFieldName("f", i))).swap).toListMap ++
                          b.zipWithIndex.map(_.map(i => BsonField.Name(createFieldName("b", i))).swap).toListMap),
                      b.zipWithIndex.map(p => docVarToExpr(DocField(BsonField.Name(createFieldName("b", p._2))))),
                      red.zipWithIndex.map(ai =>
                        (BsonField.Name(createFieldName("f", ai._2)),
                          accumulator(ai._1.as($field(createFieldName("f", ai._2)))))).toListMap))(
                    exprs => WB.groupBy(src,
                      b,
                      exprs.zipWithIndex.map(ai =>
                        (BsonField.Name(createFieldName("f", ai._2)),
                          accumulator(ai._1))).toListMap)),
                    repair)
              }).join
          case Sort(src, bucket, order) =>
            val (keys, dirs) = (bucket.toIList.map((_, SortDir.asc)) <::: order).unzip
            keys.traverse(handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, _))
              .map(ks => WB.sortBy(src, ks.toList, dirs.toList))
          case Filter(src0, cond) => {
            val selectors = getSelector[T, M, EX](
              cond, defaultSelector[T].right, selector[T](cfg.bsonVersion) ∘ (_ <+> defaultSelector[T].right))
            val typeSelectors = getSelector[T, M, EX](
              cond, InternalError.fromMsg(s"not a typecheck").left , typeSelector[T])

            def filterBuilder(src: WorkflowBuilder[WF], partialSel: PartialSelector[T]):
                M[WorkflowBuilder[WF]] = {
              val (sel, inputs) = partialSel

              inputs.traverse(f => handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, f(cond)))
                .map(WB.filter(src, _, sel))
            }

            (selectors.toOption, typeSelectors.toOption) match {
              case (None, Some(typeSel)) => filterBuilder(src0, typeSel)
              case (Some(sel), None) => filterBuilder(src0, sel)
              case (Some(sel), Some(typeSel)) => filterBuilder(src0, typeSel) >>= (filterBuilder(_, sel))
              case _ =>
                handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, cond).map {
                  // TODO: Postpone decision until we know whether we are going to
                  //       need mapReduce anyway.
                  case cond @ HasThat(_) => WB.filter(src0, List(cond), {
                    case f :: Nil => Selector.Doc(f -> Selector.Eq(Bson.Bool(true)))
                  })
                  case \&/.This(js) => WB.filter(src0, Nil, {
                    case Nil => Selector.Where(js(jscore.ident("this")).toJs)
                  })
                }
            }
          }
          case Union(src, lBranch, rBranch) =>
            (rebaseWB[T, M, WF, EX](cfg, lBranch, src) ⊛
              rebaseWB[T, M, WF, EX](cfg, rBranch, src))(
              UnionBuilder(_, _))
          case Subset(src, from, sel, count) =>
            (rebaseWB[T, M, WF, EX](cfg, from, src) ⊛
              (rebaseWB[T, M, WF, EX](cfg, count, src) >>= (HasInt[M, WF](_))))(
              sel match {
                case Drop => WB.skip
                case Take => WB.limit
                // TODO: Better sampling
                case Sample => WB.limit
              })
          case Unreferenced() =>
            CollectionBuilder($pure(Bson.Null), WorkflowBuilder.Root(), none).point[M]
        }
      }

    @SuppressWarnings(Array("org.wartremover.warts.Recursion"))
    implicit def equiJoin[T[_[_]]: BirecursiveT: EqualT: ShowT]:
        Planner.Aux[T, EquiJoin[T, ?]] =
      new Planner[EquiJoin[T, ?]] {
        type IT[G[_]] = T[G]
        def plan
          [M[_]: Monad: ExecTimeR: MonadFsErr, WF[_]: Functor: Coalesce: Crush, EX[_]: Traverse]
          (cfg: PlannerConfig[T, EX, WF])
          (implicit
            ev0: WorkflowOpCoreF :<: WF,
            ev1: RenderTree[WorkflowBuilder[WF]],
            ev2: WorkflowBuilder.Ops[WF],
            ev3: EX :<: ExprOp) =
          qs =>
        (rebaseWB[T, M, WF, EX](cfg, qs.lBranch, qs.src) ⊛
          rebaseWB[T, M, WF, EX](cfg, qs.rBranch, qs.src))(
          (lb, rb) => {
            val (lKey, rKey) = Unzip[List].unzip(qs.key)

            (lKey.traverse(handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, _)) ⊛
              rKey.traverse(handleFreeMap[T, M, EX](cfg.funcHandler, cfg.staticHandler, _)))(
              (lk, rk) =>
              liftM[M, WorkflowBuilder[WF]](cfg.joinHandler.run(
                qs.f,
                JoinSource(lb, lk),
                JoinSource(rb, rk))) >>=
                (getExprBuilder[T, M, WF, EX](cfg.funcHandler, cfg.staticHandler)(_, qs.combine >>= {
                  case LeftSide => Free.roll(MFC(MapFuncsCore.ProjectKey(HoleF, MapFuncsCore.StrLit("left"))))
                  case RightSide => Free.roll(MFC(MapFuncsCore.ProjectKey(HoleF, MapFuncsCore.StrLit("right"))))
                }))).join
          }).join
      }

    implicit def coproduct[T[_[_]], F[_], G[_]](
      implicit F: Planner.Aux[T, F], G: Planner.Aux[T, G]):
        Planner.Aux[T, Coproduct[F, G, ?]] =
      new Planner[Coproduct[F, G, ?]] {
        type IT[G[_]] = T[G]
        def plan
          [M[_]: Monad: ExecTimeR: MonadFsErr, WF[_]: Functor: Coalesce: Crush, EX[_]: Traverse]
          (cfg: PlannerConfig[T, EX, WF])
          (implicit
            ev0: WorkflowOpCoreF :<: WF,
            ev1: RenderTree[WorkflowBuilder[WF]],
            ev2: WorkflowBuilder.Ops[WF],
            ev3: EX :<: ExprOp) =
          _.run.fold(
            F.plan[M, WF, EX](cfg),
            G.plan[M, WF, EX](cfg))
      }

    // TODO: All instances below here only need to exist because of `FreeQS`,
    //       but can’t actually be called.

    def default[T[_[_]], F[_]](label: String): Planner.Aux[T, F] =
      new Planner[F] {
        type IT[G[_]] = T[G]

        def plan
          [M[_]: Monad: ExecTimeR: MonadFsErr, WF[_]: Functor: Coalesce: Crush, EX[_]: Traverse]
          (cfg: PlannerConfig[T, EX, WF])
          (implicit
            ev0: WorkflowOpCoreF :<: WF,
            ev1: RenderTree[WorkflowBuilder[WF]],
            ev2: WorkflowBuilder.Ops[WF],
            ev3: EX :<: ExprOp) =
          κ(raiseErr(qscriptPlanningFailed(InternalError.fromMsg(s"should not be reached: $label"))))
      }

    implicit def deadEnd[T[_[_]]]: Planner.Aux[T, Const[DeadEnd, ?]] =
      default("DeadEnd")

    implicit def read[T[_[_]], A]: Planner.Aux[T, Const[Read[A], ?]] =
      default("Read")

    implicit def shiftedReadDir[T[_[_]]]: Planner.Aux[T, Const[ShiftedRead[ADir], ?]] =
      default("ShiftedRead[ADir]")

    implicit def thetaJoin[T[_[_]]]: Planner.Aux[T, ThetaJoin[T, ?]] =
      default("ThetaJoin")

    implicit def projectBucket[T[_[_]]]: Planner.Aux[T, ProjectBucket[T, ?]] =
      default("ProjectBucket")
  }

  def getExpr[
    T[_[_]]: BirecursiveT: ShowT,
    M[_]: Monad: ExecTimeR: MonadFsErr, EX[_]: Traverse: Inject[?[_], ExprOp]]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX])(fm: FreeMap[T]
  ) : M[Fix[ExprOp]] =
    processMapFuncExpr[T, M, EX, Hole](funcHandler, staticHandler)(fm)(κ($$ROOT))

  def getJsFn[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr: ExecTimeR]
    (fm: FreeMap[T])
      : M[JsFn] =
    processMapFunc[T, M, Hole](fm)(κ(jscore.Ident(JsFn.defaultName))) ∘
      (JsFn(JsFn.defaultName, _))

  def getBuilder
    [T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr, WF[_], EX[_]: Traverse, A]
    (handler: FreeMapA[T, A] => M[Expr])
    (src: WorkflowBuilder[WF], fm: FreeMapA[T, A])
    (implicit ev: EX :<: ExprOp)
      : M[WorkflowBuilder[WF]] =
    fm.project match {
      case MapFuncCore.StaticMap(elems) =>
        elems.traverse(_.bitraverse({
          case Embed(MapFuncCore.EC(ejson.Str(key))) => BsonField.Name(key).point[M]
          case key => raiseErr[M, BsonField.Name](qscriptPlanningFailed(InternalError.fromMsg(s"Unsupported object key: ${key.shows}")))
        },
          handler)) ∘
        (es => DocBuilder(src, es.toListMap))
      case _ => handler(fm) ∘ (ExprBuilder(src, _))
    }

  def getExprBuilder
    [T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: ExecTimeR: MonadFsErr, WF[_], EX[_]: Traverse]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX])
    (src: WorkflowBuilder[WF], fm: FreeMap[T])
    (implicit ev: EX :<: ExprOp)
      : M[WorkflowBuilder[WF]] =
    getBuilder[T, M, WF, EX, Hole](handleFreeMap[T, M, EX](funcHandler, staticHandler, _))(src, fm)

  def getReduceBuilder
    [T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: ExecTimeR: MonadFsErr, WF[_], EX[_]: Traverse]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX])
    (src: WorkflowBuilder[WF], fm: FreeMapA[T, ReduceIndex])
    (implicit ev: EX :<: ExprOp)
      : M[WorkflowBuilder[WF]] =
    getBuilder[T, M, WF, EX, ReduceIndex](handleRedRepair[T, M, EX](funcHandler, staticHandler, _))(src, fm)

  def getJsMerge[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr: ExecTimeR]
    (jf: JoinFunc[T], a1: JsCore, a2: JsCore)
      : M[JsFn] =
    processMapFunc[T, M, JoinSide](
      jf) {
      case LeftSide => a1
      case RightSide => a2
    } ∘ (JsFn(JsFn.defaultName, _))

  def getExprMerge[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr: ExecTimeR, EX[_]: Traverse]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX])
    (jf: JoinFunc[T], a1: DocVar, a2: DocVar)
    (implicit inj: EX :<: ExprOp)
      : M[Fix[ExprOp]] =
    processMapFuncExpr[T, M, EX, JoinSide](funcHandler, staticHandler)(
      jf) {
      case LeftSide => $var(a1)
      case RightSide => $var(a2)
    }

  def exprOrJs[M[_]: Applicative: MonadFsErr, A]
    (a: A)
    (exf: A => M[Fix[ExprOp]], jsf: A => M[JsFn])
      : M[Expr] = {
    // TODO: Return _both_ errors
    val js = jsf(a)
    val expr = exf(a)
    handleErr[M, Expr](
      (js ⊛ expr)(\&/.Both(_, _)))(
      _ => handleErr[M, Expr](js.map(-\&/))(_ => expr.map(\&/-)))
  }

  def handleFreeMap[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: ExecTimeR: MonadFsErr, EX[_]: Traverse]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX], fm: FreeMap[T])
    (implicit ev: EX :<: ExprOp)
      : M[Expr] =
    exprOrJs(fm)(getExpr[T, M, EX](funcHandler, staticHandler)(_), getJsFn[T, M])

  def handleRedRepair[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: ExecTimeR: MonadFsErr, EX[_]: Traverse]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX], jr: FreeMapA[T, ReduceIndex])
    (implicit ev: EX :<: ExprOp)
      : M[Expr] =
    exprOrJs(jr)(getExprRed[T, M, EX](funcHandler, staticHandler)(_), getJsRed[T, M])

  def getExprRed[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: ExecTimeR: MonadFsErr, EX[_]: Traverse]
    (funcHandler: MapFunc[T, ?] ~> OptionFree[EX, ?], staticHandler: StaticHandler[T, EX])
    (jr: FreeMapA[T, ReduceIndex])
    (implicit ev: EX :<: ExprOp)
      : M[Fix[ExprOp]] =
    processMapFuncExpr[T, M, EX, ReduceIndex](funcHandler, staticHandler)(jr)(_.idx.fold(
      i => $field("_id", i.toString),
      i => $field(createFieldName("f", i))))

  def getJsRed[T[_[_]]: BirecursiveT: ShowT, M[_]: Monad: MonadFsErr: ExecTimeR]
    (jr: Free[MapFunc[T, ?], ReduceIndex])
      : M[JsFn] =
    processMapFunc[T, M, ReduceIndex](jr)(_.idx.fold(
      i => jscore.Select(jscore.Select(jscore.Ident(JsFn.defaultName), "_id"), i.toString),
      i => jscore.Select(jscore.Ident(JsFn.defaultName), createFieldName("f", i)))) ∘
      (JsFn(JsFn.defaultName, _))

  def rebaseWB
    [T[_[_]]: EqualT, M[_]: Monad: ExecTimeR: MonadFsErr, WF[_]: Functor: Coalesce: Crush, EX[_]: Traverse]
    (cfg: PlannerConfig[T, EX, WF],
      free: FreeQS[T],
      src: WorkflowBuilder[WF])
    (implicit
      F: Planner.Aux[T, QScriptTotal[T, ?]],
      ev0: WorkflowOpCoreF :<: WF,
      ev1: RenderTree[WorkflowBuilder[WF]],
      ev2: WorkflowBuilder.Ops[WF],
      ev3: EX :<: ExprOp)
      : M[WorkflowBuilder[WF]] =
    free.cataM(
      interpretM[M, QScriptTotal[T, ?], qscript.Hole, WorkflowBuilder[WF]](κ(src.point[M]), F.plan(cfg)))

  // TODO: Need `Delay[Show, WorkflowBuilder]`
  @SuppressWarnings(Array("org.wartremover.warts.ToString"))
  def HasLiteral[M[_]: Applicative: MonadFsErr, WF[_]]
    (wb: WorkflowBuilder[WF])
    (implicit ev0: WorkflowOpCoreF :<: WF)
      : M[Bson] =
    asLiteral(wb).fold(
      raiseErr[M, Bson](qscriptPlanningFailed(NonRepresentableEJson(wb.toString))))(
      _.point[M])

  @SuppressWarnings(Array("org.wartremover.warts.ToString"))
  def HasInt[M[_]: Monad: MonadFsErr, WF[_]]
    (wb: WorkflowBuilder[WF])
    (implicit ev0: WorkflowOpCoreF :<: WF)
      : M[Long] =
    HasLiteral[M, WF](wb) >>= {
      case Bson.Int32(v) => v.toLong.point[M]
      case Bson.Int64(v) => v.point[M]
      case x => raiseErr(qscriptPlanningFailed(NonRepresentableEJson(x.toString)))
    }

  // This is maybe worth putting in Matryoshka?
  def findFirst[T[_[_]]: RecursiveT, F[_]: Functor: Foldable, A](
    f: PartialFunction[T[F], A]):
      CoalgebraM[A \/ ?, F, T[F]] =
    tf => (f.lift(tf) \/> tf.project).swap

  // TODO: This should perhaps be _in_ PhaseResults or something
  def log[M[_]: Monad, A: RenderTree]
    (label: String, ma: M[A])
    (implicit mtell: MonadTell_[M, PhaseResults])
      : M[A] =
    ma.mproduct(a => mtell.tell(Vector(PhaseResult.tree(label, a)))) ∘ (_._1)

  def toMongoQScript[
      T[_[_]]: BirecursiveT: EqualT: RenderTreeT: ShowT,
      M[_]: Monad: MonadFsErr: PhaseResultTell]
      (anyDoc: Collection => OptionT[M, BsonDocument],
        qs: T[fs.MongoQScript[T, ?]])
      (implicit BR: Branches[T, fs.MongoQScript[T, ?]])
      : M[T[fs.MongoQScript[T, ?]]] = {

    type MQS[A] = fs.MongoQScript[T, A]
    type QST[A] = QScriptTotal[T, A]

    val O = new Optimize[T]
    val R = new Rewrite[T]

    def normalize(mqs: T[MQS]): M[T[MQS]] = {
      val mqs1 = mqs.transCata[T[MQS]](R.normalizeEJ[MQS])
      val mqs2 = BR.branches.modify(
          _.transCata[FreeQS[T]](liftCo(R.normalizeEJCoEnv[QScriptTotal[T, ?]]))
        )(mqs1.project).embed
      Trans(assumeReadType[T, MQS, M](Type.AnyObject), mqs2)
    }

    // TODO: All of these need to be applied through branches. We may also be able to compose
    //       them with normalization as the last step and run until fixpoint. Currently plans are
    //       too sensitive to the order in which these are applied.
    //       Some constraints:
    //       - elideQuasarSigil should only be applied once
    //       - elideQuasarSigil needs assumeReadType to be applied in order to
    //         work properly in all cases
    //       - R.normalizeEJ/R.normalizeEJCoEnv may change the structure such
    //         that assumeReadType can elide more guards
    //         E.g. Map(x, SrcHole) is normalized into x. assumeReadType does
    //         not recognize any Map as shape preserving, but it may recognize
    //         x being shape preserving (e.g. when x = ShiftedRead(y, ExcludeId))
    for {
      mongoQS1 <- Trans(assumeReadType[T, MQS, M](Type.AnyObject), qs)
      mongoQS2 <- mongoQS1.transCataM(elideQuasarSigil[T, MQS, M](anyDoc))
      mongoQS3 <- normalize(mongoQS2)
      _ <- BackendModule.logPhase[M](PhaseResult.treeAndCode("QScript Mongo", mongoQS3))

      mongoQS4 =  mongoQS3.transCata[T[MQS]](
                    liftFF[QScriptCore[T, ?], MQS, T[MQS]](
                      repeatedly(O.subsetBeforeMap[MQS, MQS](
                        reflNT[MQS]))))
      _ <- BackendModule.logPhase[M](
             PhaseResult.treeAndCode("QScript Mongo (Subset Before Map)",
             mongoQS4))

      // TODO: Once field deletion is implemented for 3.4, this could be selectively applied, if necessary.
      mongoQS5 =  PreferProjection.preferProjection[MQS](mongoQS4)
      _ <- BackendModule.logPhase[M](PhaseResult.treeAndCode("QScript Mongo (Prefer Projection)", mongoQS5))
    } yield mongoQS5
  }

  def buildWorkflow
    [T[_[_]]: BirecursiveT: EqualT: RenderTreeT: ShowT,
      M[_]: Monad: PhaseResultTell: MonadFsErr: ExecTimeR,
      WF[_]: Functor: Coalesce: Crush,
      EX[_]: Traverse]
    (cfg: PlannerConfig[T, EX, WF])
    (qs: T[fs.MongoQScript[T, ?]])
    (implicit
      ev0: WorkflowOpCoreF :<: WF,
      ev1: EX :<: ExprOp,
      ev2: RenderTree[Fix[WF]])
      : M[Fix[WF]] =
    for {
      wb <- log(
        "Workflow Builder",
        qs.cataM[M, WorkflowBuilder[WF]](
          Planner[T, fs.MongoQScript[T, ?]].plan[M, WF, EX](cfg).apply(_) ∘
            (_.transCata[Fix[WorkflowBuilderF[WF, ?]]](repeatedly(WorkflowBuilder.normalize[WF, Fix[WorkflowBuilderF[WF, ?]]])))))
      wf <- log("Workflow (raw)", liftM[M, Fix[WF]](WorkflowBuilder.build[WBM, WF](wb)))
    } yield wf

  def plan0
    [T[_[_]]: BirecursiveT: EqualT: RenderTreeT: ShowT,
      M[_]: Monad: PhaseResultTell: MonadFsErr: ExecTimeR,
      WF[_]: Traverse: Coalesce: Crush: Crystallize,
      EX[_]: Traverse]
    (anyDoc: Collection => OptionT[M, BsonDocument],
      cfg: PlannerConfig[T, EX, WF])
    (qs: T[fs.MongoQScript[T, ?]])
    (implicit
      ev0: WorkflowOpCoreF :<: WF,
      ev1: WorkflowBuilder.Ops[WF],
      ev2: EX :<: ExprOp,
      ev3: RenderTree[Fix[WF]])
      : M[Crystallized[WF]] = {

    def doBuildWorkflow[F[_]: Monad: ExecTimeR](qs0: T[fs.MongoQScript[T, ?]]) =
      buildWorkflow[T, FileSystemErrT[PhaseResultT[F, ?], ?], WF, EX](cfg)(qs0).run.run

    for {
      qs0 <- toMongoQScript[T, M](anyDoc, qs)
      logRes0 <- doBuildWorkflow[M](qs0)
      (log0, res0) = logRes0
      wf0 <- res0 match {
               case \/-(wf) if (needsMapBeforeSort(wf)) =>
                 // TODO look into adding mapBeforeSort to WorkflowBuilder or Workflow stage
                 // instead, so that we can avoid having to rerun some transformations.
                 // See #3063
                 log("QScript Mongo (Map Before Sort)",
                   Trans(mapBeforeSort[T, M], qs0)) >>= buildWorkflow[T, M, WF, EX](cfg)
               case \/-(wf) =>
                 PhaseResultTell[M].tell(log0) *> wf.point[M]
               case -\/(err) =>
                 PhaseResultTell[M].tell(log0) *> raiseErr[M, Fix[WF]](err)
             }
      wf1 <- log(
        "Workflow (crystallized)",
        Crystallize[WF].crystallize(wf0).point[M])
    } yield wf1
  }

  def planExecTime[
      T[_[_]]: BirecursiveT: EqualT: ShowT: RenderTreeT,
      M[_]: Monad: PhaseResultTell: MonadFsErr](
      qs: T[fs.MongoQScript[T, ?]],
      queryContext: fs.QueryContext,
      queryModel: MongoQueryModel,
      anyDoc: Collection => OptionT[M, BsonDocument],
      execTime: Instant)
      : M[Crystallized[WorkflowF]] = {
    val peek = anyDoc andThen (_.mapT(_.liftM[ReaderT[?[_], Instant, ?]]))
    plan[T, ReaderT[M, Instant, ?]](qs, queryContext, queryModel, peek).run(execTime)
  }

  /** Translate the QScript plan to an executable MongoDB "physical"
    * plan, taking into account the current runtime environment as captured by
    * the given context.
    *
    * Internally, the type of the plan being built constrains which operators
    * can be used, but the resulting plan uses the largest, common type so that
    * callers don't need to worry about it.
    *
    * @param anyDoc returns any document in the given `Collection`
    */
  def plan[
      T[_[_]]: BirecursiveT: EqualT: ShowT: RenderTreeT,
      M[_]: Monad: PhaseResultTell: MonadFsErr: ExecTimeR](
      qs: T[fs.MongoQScript[T, ?]],
      queryContext: fs.QueryContext,
      queryModel: MongoQueryModel,
      anyDoc: Collection => OptionT[M, BsonDocument])
      : M[Crystallized[WorkflowF]] = {
    import MongoQueryModel._

    val bsonVersion = toBsonVersion(queryModel)

    val joinHandler: JoinHandler[Workflow3_2F, WBM] =
      JoinHandler.fallback[Workflow3_2F, WBM](
        JoinHandler.pipeline(queryContext.statistics, queryContext.indexes),
        JoinHandler.mapReduce)

    queryModel match {
      case `3.4.4` =>
        val cfg = PlannerConfig[T, Expr3_4_4, Workflow3_2F](
          joinHandler,
          FuncHandler.handle3_4_4(bsonVersion),
          StaticHandler.handle,
          bsonVersion)
        plan0[T, M, Workflow3_2F, Expr3_4_4](anyDoc, cfg)(qs)

      case `3.4` =>
        val cfg = PlannerConfig[T, Expr3_4, Workflow3_2F](
          joinHandler,
          FuncHandler.handle3_4(bsonVersion),
          StaticHandler.handle,
          bsonVersion)
        plan0[T, M, Workflow3_2F, Expr3_4](anyDoc, cfg)(qs)

      case `3.2` =>
        val cfg = PlannerConfig[T, Expr3_2, Workflow3_2F](
          joinHandler,
          FuncHandler.handle3_2(bsonVersion),
          StaticHandler.handle,
          bsonVersion)
        plan0[T, M, Workflow3_2F, Expr3_2](anyDoc, cfg)(qs)

    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy