{"action":"created","comment":{"url":"https://api.github.com/repos/quasar-analytics/quasar/pulls/comments/152334110","pull_request_review_id":78180244,"id":152334110,"diff_hunk":"@@ -16,103 +16,272 @@\n \n package quasar.qscript.qsu\n \n-import quasar.NameGenerator\n+import quasar.{NameGenerator, Planner}, Planner.PlannerErrorME\n+import quasar.contrib.matryoshka._\n import quasar.contrib.scalaz.MonadState_\n+import quasar.ejson.{EJson, Fixed}\n+import quasar.ejson.implicits._\n+import quasar.fp._\n+import quasar.fp.ski.κ\n import quasar.qscript.{\n+  construction,\n   Center,\n   Hole,\n+  HoleF,\n   LeftSide,\n   LeftSide3,\n+  ReduceIndex,\n   RightSide,\n   RightSide3,\n   SrcHole\n }\n import quasar.qscript.qsu.{QScriptUniform => QSU}\n-import slamdata.Predef._\n+import slamdata.Predef.{Map => SMap, _}\n \n-import matryoshka.BirecursiveT\n-import scalaz.{Free, Monad, StateT}\n-import scalaz.syntax.foldable._\n-import scalaz.syntax.monad._\n+import matryoshka.{delayEqual, BirecursiveT, EqualT}\n+import matryoshka.data.free._\n+import scalaz.{Equal, Free, Monad, Scalaz, StateT}, Scalaz._   // sigh, monad/traverse conflict\n \n-final class MinimizeAutoJoins[T[_[_]]: BirecursiveT] private () extends QSUTTypes[T] {\n+final class MinimizeAutoJoins[T[_[_]]: BirecursiveT: EqualT] private () extends QSUTTypes[T] {\n+  import ApplyProvenance.AuthenticatedQSU\n   import QSUGraph.Extractors._\n \n-  def apply[F[_]: Monad: NameGenerator](qgraph: QSUGraph): F[QSUGraph] = {\n-    type G[A] = StateT[F, RevIdx, A]\n+  private val func = construction.Func[T]\n+  private val srcHole: Hole = SrcHole   // wtb smart constructor\n \n-    val back = qgraph rewriteM {\n-      case qgraph @ AutoJoin2(left, right, combiner) =>\n-        val fml = MappableRegion.maximal(left)\n-        val fmr = MappableRegion.maximal(right)\n+  private val J = Fixed[T[EJson]]\n+\n+  private val AP = ApplyProvenance[T]\n+  private val QP = QProv[T]\n+\n+  // needed to avoid bug in implicit search!  don't import QP.prov._\n+  private implicit val QPEq: Equal[QP.P] =\n+    QP.prov.provenanceEqual(scala.Predef.implicitly, Equal[FreeMapA[Access[Hole]]])\n \n-        val minimized = minimizeSources(fml.toList ::: fmr.toList)\n+  def apply[F[_]: Monad: NameGenerator: PlannerErrorME](agraph: AuthenticatedQSU[T]): F[AuthenticatedQSU[T]] = {\n+    type G[A] = StateT[StateT[F, RevIdx, ?], QSUDims[T], A]\n \n-        lazy val extended = combiner map {\n-          case LeftSide => fml.map(_ => SrcHole: Hole)\n-          case RightSide => fmr.map(_ => SrcHole: Hole)\n+    val back = agraph.graph rewriteM {\n+      case qgraph @ AutoJoin2(left, right, combiner) =>\n+        val combiner2 = combiner map {\n+          case LeftSide => 0\n+          case RightSide => 1\n         }\n \n-        attemptCoalesce[G](qgraph, minimized, extended)\n+        coalesceToMap[G](qgraph, List(left, right), Free.liftF[MapFunc, Int](combiner2))\n \n       case qgraph @ AutoJoin3(left, center, right, combiner) =>\n-        val fml = MappableRegion.maximal(left)\n-        val fmc = MappableRegion.maximal(center)\n-        val fmr = MappableRegion.maximal(right)\n+        val combiner2 = combiner map {\n+          case LeftSide3 => 0\n+          case Center => 1\n+          case RightSide3 => 2\n+        }\n \n-        val minimized =\n-          minimizeSources(fml.toList ::: fmc.toList ::: fmr.toList)\n+        coalesceToMap[G](qgraph, List(left, center, right), Free.liftF[MapFunc, Int](combiner2))\n+    }\n \n-        lazy val extended = combiner map {\n-          case LeftSide3 => fml.map(_ => SrcHole: Hole)\n-          case Center => fmc.map(_ => SrcHole: Hole)\n-          case RightSide3 => fmr.map(_ => SrcHole: Hole)\n-        }\n+    val lifted = back(agraph.dims) map {\n+      case (dims, graph) => AuthenticatedQSU[T](graph, dims)\n+    }\n \n-        attemptCoalesce[G](qgraph, minimized, extended)\n+    lifted.eval(agraph.graph.generateRevIndex)\n+  }\n+\n+  // the Ints are indices into branches\n+  private def coalesceToMap[\n+      G[_]: Monad: NameGenerator: MonadState_[?[_], RevIdx]: MonadState_[?[_], QSUDims[T]]: PlannerErrorME](\n+      qgraph: QSUGraph,\n+      branches: List[QSUGraph],\n+      combiner: FreeMapA[Int]): G[QSUGraph] = {\n+\n+    val fms = branches.zipWithIndex map {\n+      case (g, i) =>\n+        expandSecondOrder(MappableRegion.maximal(g)).map(g => (g, i))\n     }\n \n-    back.eval(qgraph.generateRevIndex)\n+    val (remap, candidates) = minimizeSources(fms.flatMap(_.toList))\n+\n+    lazy val fm = combiner.flatMap(i => fms(i).map(κ(remap(i))))\n+\n+    coalesceRoots[G](qgraph, fm, candidates)\n+  }\n+\n+  // attempt to extend by seeing through constructs like filter (mostly just filter)\n+  @SuppressWarnings(Array(\"org.wartremover.warts.Recursion\"))\n+  private def expandSecondOrder(fm: FreeMapA[QSUGraph]): FreeMapA[QSUGraph] = {\n+    fm\n+\n+    // TODO the use of this function is incorrect right now\n+    // it will be employed even when joins are avoidable in\n+    // other ways (e.g. by eliminating Unreferenced())\n+\n+    /*fm flatMap {\n+      case QSFilter(source, predicate) =>\n+        // tune into 102.5 FM, The Source\n+        val sourceFM = expandSecondOrder(MappableRegion.maximal(source))\n+        func.Cond(predicate.flatMap(κ(sourceFM)), sourceFM, func.Undefined[QSUGraph])\n+\n+      // TODO should we handle LPFilter here just for completion sake?\n+\n+      case g => Free.pure(g)\n+    }*/\n   }\n \n-  private def attemptCoalesce[G[_]: Monad: NameGenerator: MonadState_[?[_], RevIdx]](\n+  // attempts to reduce the set of candidates to a single Map node, given a FreeMap[Int]\n+  // the Int indexes into the final number of distinct roots\n+  @SuppressWarnings(Array(\"org.wartremover.warts.TraversableOps\"))\n+  private def coalesceRoots[\n+      G[_]: Monad: NameGenerator: MonadState_[?[_], RevIdx]: MonadState_[?[_], QSUDims[T]]: PlannerErrorME](\n       qgraph: QSUGraph,\n-      minimized: List[QSUGraph],\n-      extended: => MapFunc[FreeMap]): G[QSUGraph] = {\n+      fm: => FreeMapA[Int],\n+      candidates: List[QSUGraph]): G[QSUGraph] = candidates match {\n \n-    lazy val fm = Free.roll[MapFunc, Hole](extended)\n+    case Nil =>\n+      updateGraph[G](QSU.Unreferenced[T, Symbol]()) map { unref =>\n+        qgraph.overwriteAtRoot(QSU.Map[T, Symbol](unref.root, fm.map(κ(srcHole)))) :++ unref\n+      }\n \n-    minimized match {\n-      case Nil =>\n-        QSUGraph.withName[T, G](QSU.Unreferenced[T, Symbol]()) map { unref =>\n-          qgraph.overwriteAtRoot(QSU.Map[T, Symbol](unref.root, fm)) :++ unref\n-        }\n+    case single :: Nil =>\n+      // if this is false, it's an assertion error\n+      // lazy val sanityCheck = fm.toList.forall(0 ==)\n \n-      case single :: Nil =>\n-        qgraph.overwriteAtRoot(QSU.Map[T, Symbol](single.root, fm)).point[G]\n+      qgraph.overwriteAtRoot(QSU.Map[T, Symbol](single.root, fm.map(κ(srcHole)))).point[G]\n \n-      case minimized =>\n-        // TODO implement coalesce heuristics here\n+    case candidates =>\n+      val reducerAttempt = candidates collect {\n+        case g @ QSReduce(source, buckets, reducers, repair) =>\n+          (source, buckets, reducers, repair)\n+      }\n+\n+      // candidates.forall(_ ~= QSReduce)\n+      if (reducerAttempt.lengthCompare(candidates.length) === 0) {\n+        for {\n+          // apply coalescence recursively to our sources\n+          extended <- reducerAttempt traverse {\n+            case desc @ (source, buckets, reducers, repair) =>\n+              // TODO this is a weird use of the function, but it should work\n+              // we're just trying to run ourselves recursively to extend the sources\n+              // this isn't likely to be a common case\n+              coalesceToMap[G](source, List(source), Free.pure(0)) map {\n+                // coalesceToMap is always going to return... a Map, but\n+                // what we care about is the source and the fm, not the\n+                // node itself.  so we pull it apart (this is what's weird, btw)\n+                //\n+                // TODO short circuit this a bit if fm === Free.pure(Hole)\n+                // I'm pretty sure the fallthrough case will never be hit\n+                case Map(source2, fm) =>\n+                  (\n+                    source2,\n+                    buckets,    // buckets are based on access, so we don't need to modify here\n+                    reducers.map(_.map(_.flatMap(κ(fm)))),\n+                    repair)\n+\n+                case _ => desc\n+              }\n+          }\n+\n+          // we know we're non-empty by structure of outer match\n+          (source, buckets, _, _) = extended.head\n+\n+          dims <- MonadState_[G, QSUDims[T]].get\n+\n+          // do we have the same provenance at our roots?\n+          rootProvCheck = extended.forall(t => dims(t._1.root) === dims(source.root))\n+\n+          // we need a stricter check than just provenance, since we have to inline maps\n+          back <- if (rootProvCheck && extended.forall(_._1.root === source.root)) {\n+            val lifted = extended.zipWithIndex map {\n+              case ((_, buckets, reducers, repair), i) =>\n+                (\n+                  buckets,\n+                  reducers,\n+                  // we use maps here to avoid definedness issues in reduction results\n+                  func.MakeMap(func.Constant(J.str(i.toString)), repair))\n+            }\n+\n+            // this is fine, because we can't be here if candidates is empty\n+            // doing it this way avoids an extra (and useless) Option state in the fold\n+            val (_, lhreducers, lhrepair) = lifted.head\n+\n+            // squish all the reducers down into lhead\n+            val (_, (reducers, repair)) =\n+              lifted.tail.foldLeft((lhreducers.length, (lhreducers, lhrepair))) {\n+                case ((roffset, (lreducers, lrepair)), (_, rreducers, rrepair)) =>\n+                  val reducers = lreducers ::: rreducers\n+\n+                  val roffset2 = roffset + rreducers.length\n+\n+                  val repair = func.ConcatMaps(\n+                    lrepair,\n+                    rrepair map {\n+                      case ReduceIndex(e) =>\n+                        ReduceIndex(e.rightMap(_ + roffset))\n+                    })\n+\n+                  (roffset2, (reducers, repair))\n+              }\n+\n+            // 107.7, All chiropractors, all the time\n+            val adjustedFM = fm flatMap { i =>\n+              // get the value back OUT of the map\n+              func.ProjectKey(HoleF[T], func.Constant(J.str(i.toString)))\n+            }\n+\n+            val redPat = QSU.QSReduce[T, Symbol](source.root, buckets, reducers, repair)\n+\n+            updateGraph[G](redPat) map { red =>\n+              qgraph.overwriteAtRoot(QSU.Map[T, Symbol](red.root, adjustedFM)) :++ red\n+            }\n+          } else {\n+            qgraph.point[G]\n+          }\n+        } yield back\n+      } else {\n         qgraph.point[G]\n-    }\n+      }\n   }\n \n-  private def minimizeSources(sources: List[QSUGraph]): List[QSUGraph] = {\n-    val (_, minimized) = sources.foldRight((Set[Symbol](), List[QSUGraph]())) {\n-      // just drop unreferenced sources entirely\n-      case (Unreferenced(), pair) => pair\n+  // we return a remap function along with a minimized list\n+  // the remapper can be used to determine which input indices\n+  // collapsed into what output indices\n+  // note that if everything is Unreferenced, remap will be κ(-1),\n+  // which is weird but harmless\n+  private def minimizeSources(sources: List[(QSUGraph, Int)]): (Int => Int, List[QSUGraph]) = {\n+    val (_, remap, offset, minimized) =\n+      sources.foldRight((Set[Symbol](), SMap[Int, Int](), 0, List[QSUGraph]())) {\n+        // just drop unreferenced sources entirely\n+        case ((Unreferenced(), i), (mask, remap, offset, acc)) =>\n+          (mask, remap + (i -> offset), offset, acc)\n \n-      case (g, (mask, acc)) =>\n-        if (mask(g.root))\n-          (mask, acc)\n-        else\n-          (mask + g.root, g :: acc)\n-    }\n+        case ((g, i), (mask, remap, offset, acc)) =>\n+          if (mask(g.root))\n+            (mask, remap + (i -> offset), offset, acc)\n+          else\n+            (mask + g.root, remap + (i -> offset), offset + 1, g :: acc)\n+      }\n+\n+    // if we're asking for a non-existent remap index, it's because it isn't referenced\n+    // oh yeah, and we have to complement the remap because of the foldRight\n+    // the - 1 here comes from the fact that we over-increment offset above\n+    (i => remap.mapValues(offset - 1 - _).getOrElse(i, -1), minimized)\n+  }\n+\n+  private def updateGraph[\n+      G[_]: Monad: NameGenerator: MonadState_[?[_], RevIdx]: MonadState_[?[_], QSUDims[T]]: PlannerErrorME](\n+      pat: QScriptUniform[Symbol]): G[QSUGraph] = {\n+\n+    for {\n+      qgraph <- QSUGraph.withName[T, G](pat)\n \n-    minimized\n+      dims <- MonadState_[G, QSUDims[T]].get\n+      computed <- AP.computeProvenanceƒ[G].apply(QSUGraph.QSUPattern(qgraph.root, pat.map(dims)))","path":"connector/src/main/scala/quasar/qscript/qsu/MinimizeAutoJoins.scala","position":317,"original_position":317,"commit_id":"d9e8ee0a9f76b68b17b0a3ffada14b4e575d2861","original_commit_id":"d9e8ee0a9f76b68b17b0a3ffada14b4e575d2861","user":{"login":"djspiewak","id":752,"avatar_url":"https://avatars1.githubusercontent.com/u/752?v=4","gravatar_id":"","url":"https://api.github.com/users/djspiewak","html_url":"https://github.com/djspiewak","followers_url":"https://api.github.com/users/djspiewak/followers","following_url":"https://api.github.com/users/djspiewak/following{/other_user}","gists_url":"https://api.github.com/users/djspiewak/gists{/gist_id}","starred_url":"https://api.github.com/users/djspiewak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/djspiewak/subscriptions","organizations_url":"https://api.github.com/users/djspiewak/orgs","repos_url":"https://api.github.com/users/djspiewak/repos","events_url":"https://api.github.com/users/djspiewak/events{/privacy}","received_events_url":"https://api.github.com/users/djspiewak/received_events","type":"User","site_admin":false},"body":"Ah interesting. I’ll fix that","created_at":"2017-11-21T16:44:02Z","updated_at":"2017-11-21T16:44:02Z","html_url":"https://github.com/quasar-analytics/quasar/pull/3137#discussion_r152334110","pull_request_url":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/quasar-analytics/quasar/pulls/comments/152334110"},"html":{"href":"https://github.com/quasar-analytics/quasar/pull/3137#discussion_r152334110"},"pull_request":{"href":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137"}},"in_reply_to_id":152304802},"pull_request":{"url":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137","id":153774905,"html_url":"https://github.com/quasar-analytics/quasar/pull/3137","diff_url":"https://github.com/quasar-analytics/quasar/pull/3137.diff","patch_url":"https://github.com/quasar-analytics/quasar/pull/3137.patch","issue_url":"https://api.github.com/repos/quasar-analytics/quasar/issues/3137","number":3137,"state":"open","locked":false,"title":"All my things","user":{"login":"djspiewak","id":752,"avatar_url":"https://avatars1.githubusercontent.com/u/752?v=4","gravatar_id":"","url":"https://api.github.com/users/djspiewak","html_url":"https://github.com/djspiewak","followers_url":"https://api.github.com/users/djspiewak/followers","following_url":"https://api.github.com/users/djspiewak/following{/other_user}","gists_url":"https://api.github.com/users/djspiewak/gists{/gist_id}","starred_url":"https://api.github.com/users/djspiewak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/djspiewak/subscriptions","organizations_url":"https://api.github.com/users/djspiewak/orgs","repos_url":"https://api.github.com/users/djspiewak/repos","events_url":"https://api.github.com/users/djspiewak/events{/privacy}","received_events_url":"https://api.github.com/users/djspiewak/received_events","type":"User","site_admin":false},"body":"Merge this one, not the others.  :-D","created_at":"2017-11-21T01:18:20Z","updated_at":"2017-11-21T16:44:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9fefd7cda45a7f629c65ab3880d70806d441f1e5","assignee":{"login":"wemrysi","id":32185,"avatar_url":"https://avatars3.githubusercontent.com/u/32185?v=4","gravatar_id":"","url":"https://api.github.com/users/wemrysi","html_url":"https://github.com/wemrysi","followers_url":"https://api.github.com/users/wemrysi/followers","following_url":"https://api.github.com/users/wemrysi/following{/other_user}","gists_url":"https://api.github.com/users/wemrysi/gists{/gist_id}","starred_url":"https://api.github.com/users/wemrysi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wemrysi/subscriptions","organizations_url":"https://api.github.com/users/wemrysi/orgs","repos_url":"https://api.github.com/users/wemrysi/repos","events_url":"https://api.github.com/users/wemrysi/events{/privacy}","received_events_url":"https://api.github.com/users/wemrysi/received_events","type":"User","site_admin":false},"assignees":[{"login":"djspiewak","id":752,"avatar_url":"https://avatars1.githubusercontent.com/u/752?v=4","gravatar_id":"","url":"https://api.github.com/users/djspiewak","html_url":"https://github.com/djspiewak","followers_url":"https://api.github.com/users/djspiewak/followers","following_url":"https://api.github.com/users/djspiewak/following{/other_user}","gists_url":"https://api.github.com/users/djspiewak/gists{/gist_id}","starred_url":"https://api.github.com/users/djspiewak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/djspiewak/subscriptions","organizations_url":"https://api.github.com/users/djspiewak/orgs","repos_url":"https://api.github.com/users/djspiewak/repos","events_url":"https://api.github.com/users/djspiewak/events{/privacy}","received_events_url":"https://api.github.com/users/djspiewak/received_events","type":"User","site_admin":false},{"login":"wemrysi","id":32185,"avatar_url":"https://avatars3.githubusercontent.com/u/32185?v=4","gravatar_id":"","url":"https://api.github.com/users/wemrysi","html_url":"https://github.com/wemrysi","followers_url":"https://api.github.com/users/wemrysi/followers","following_url":"https://api.github.com/users/wemrysi/following{/other_user}","gists_url":"https://api.github.com/users/wemrysi/gists{/gist_id}","starred_url":"https://api.github.com/users/wemrysi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wemrysi/subscriptions","organizations_url":"https://api.github.com/users/wemrysi/orgs","repos_url":"https://api.github.com/users/wemrysi/repos","events_url":"https://api.github.com/users/wemrysi/events{/privacy}","received_events_url":"https://api.github.com/users/wemrysi/received_events","type":"User","site_admin":false},{"login":"alissapajer","id":1088100,"avatar_url":"https://avatars2.githubusercontent.com/u/1088100?v=4","gravatar_id":"","url":"https://api.github.com/users/alissapajer","html_url":"https://github.com/alissapajer","followers_url":"https://api.github.com/users/alissapajer/followers","following_url":"https://api.github.com/users/alissapajer/following{/other_user}","gists_url":"https://api.github.com/users/alissapajer/gists{/gist_id}","starred_url":"https://api.github.com/users/alissapajer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alissapajer/subscriptions","organizations_url":"https://api.github.com/users/alissapajer/orgs","repos_url":"https://api.github.com/users/alissapajer/repos","events_url":"https://api.github.com/users/alissapajer/events{/privacy}","received_events_url":"https://api.github.com/users/alissapajer/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137/commits","review_comments_url":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137/comments","review_comment_url":"https://api.github.com/repos/quasar-analytics/quasar/pulls/comments{/number}","comments_url":"https://api.github.com/repos/quasar-analytics/quasar/issues/3137/comments","statuses_url":"https://api.github.com/repos/quasar-analytics/quasar/statuses/d9e8ee0a9f76b68b17b0a3ffada14b4e575d2861","head":{"label":"djspiewak:pr/mega-merge","ref":"pr/mega-merge","sha":"d9e8ee0a9f76b68b17b0a3ffada14b4e575d2861","user":{"login":"djspiewak","id":752,"avatar_url":"https://avatars1.githubusercontent.com/u/752?v=4","gravatar_id":"","url":"https://api.github.com/users/djspiewak","html_url":"https://github.com/djspiewak","followers_url":"https://api.github.com/users/djspiewak/followers","following_url":"https://api.github.com/users/djspiewak/following{/other_user}","gists_url":"https://api.github.com/users/djspiewak/gists{/gist_id}","starred_url":"https://api.github.com/users/djspiewak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/djspiewak/subscriptions","organizations_url":"https://api.github.com/users/djspiewak/orgs","repos_url":"https://api.github.com/users/djspiewak/repos","events_url":"https://api.github.com/users/djspiewak/events{/privacy}","received_events_url":"https://api.github.com/users/djspiewak/received_events","type":"User","site_admin":false},"repo":{"id":83488997,"name":"quasar","full_name":"djspiewak/quasar","owner":{"login":"djspiewak","id":752,"avatar_url":"https://avatars1.githubusercontent.com/u/752?v=4","gravatar_id":"","url":"https://api.github.com/users/djspiewak","html_url":"https://github.com/djspiewak","followers_url":"https://api.github.com/users/djspiewak/followers","following_url":"https://api.github.com/users/djspiewak/following{/other_user}","gists_url":"https://api.github.com/users/djspiewak/gists{/gist_id}","starred_url":"https://api.github.com/users/djspiewak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/djspiewak/subscriptions","organizations_url":"https://api.github.com/users/djspiewak/orgs","repos_url":"https://api.github.com/users/djspiewak/repos","events_url":"https://api.github.com/users/djspiewak/events{/privacy}","received_events_url":"https://api.github.com/users/djspiewak/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/djspiewak/quasar","description":"A NoSQL analytics engine that embraces post-relational analytics and pushes computation to the data.","fork":true,"url":"https://api.github.com/repos/djspiewak/quasar","forks_url":"https://api.github.com/repos/djspiewak/quasar/forks","keys_url":"https://api.github.com/repos/djspiewak/quasar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/djspiewak/quasar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/djspiewak/quasar/teams","hooks_url":"https://api.github.com/repos/djspiewak/quasar/hooks","issue_events_url":"https://api.github.com/repos/djspiewak/quasar/issues/events{/number}","events_url":"https://api.github.com/repos/djspiewak/quasar/events","assignees_url":"https://api.github.com/repos/djspiewak/quasar/assignees{/user}","branches_url":"https://api.github.com/repos/djspiewak/quasar/branches{/branch}","tags_url":"https://api.github.com/repos/djspiewak/quasar/tags","blobs_url":"https://api.github.com/repos/djspiewak/quasar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/djspiewak/quasar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/djspiewak/quasar/git/refs{/sha}","trees_url":"https://api.github.com/repos/djspiewak/quasar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/djspiewak/quasar/statuses/{sha}","languages_url":"https://api.github.com/repos/djspiewak/quasar/languages","stargazers_url":"https://api.github.com/repos/djspiewak/quasar/stargazers","contributors_url":"https://api.github.com/repos/djspiewak/quasar/contributors","subscribers_url":"https://api.github.com/repos/djspiewak/quasar/subscribers","subscription_url":"https://api.github.com/repos/djspiewak/quasar/subscription","commits_url":"https://api.github.com/repos/djspiewak/quasar/commits{/sha}","git_commits_url":"https://api.github.com/repos/djspiewak/quasar/git/commits{/sha}","comments_url":"https://api.github.com/repos/djspiewak/quasar/comments{/number}","issue_comment_url":"https://api.github.com/repos/djspiewak/quasar/issues/comments{/number}","contents_url":"https://api.github.com/repos/djspiewak/quasar/contents/{+path}","compare_url":"https://api.github.com/repos/djspiewak/quasar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/djspiewak/quasar/merges","archive_url":"https://api.github.com/repos/djspiewak/quasar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/djspiewak/quasar/downloads","issues_url":"https://api.github.com/repos/djspiewak/quasar/issues{/number}","pulls_url":"https://api.github.com/repos/djspiewak/quasar/pulls{/number}","milestones_url":"https://api.github.com/repos/djspiewak/quasar/milestones{/number}","notifications_url":"https://api.github.com/repos/djspiewak/quasar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/djspiewak/quasar/labels{/name}","releases_url":"https://api.github.com/repos/djspiewak/quasar/releases{/id}","deployments_url":"https://api.github.com/repos/djspiewak/quasar/deployments","created_at":"2017-02-28T23:16:24Z","updated_at":"2017-02-28T23:16:28Z","pushed_at":"2017-11-21T01:38:50Z","git_url":"git://github.com/djspiewak/quasar.git","ssh_url":"git@github.com:djspiewak/quasar.git","clone_url":"https://github.com/djspiewak/quasar.git","svn_url":"https://github.com/djspiewak/quasar","homepage":"http://quasar-analytics.org","size":63699,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"quasar-analytics:qsu","ref":"qsu","sha":"ea3361df8c0947fafb634a21f60454c57d2808b7","user":{"login":"quasar-analytics","id":15095906,"avatar_url":"https://avatars1.githubusercontent.com/u/15095906?v=4","gravatar_id":"","url":"https://api.github.com/users/quasar-analytics","html_url":"https://github.com/quasar-analytics","followers_url":"https://api.github.com/users/quasar-analytics/followers","following_url":"https://api.github.com/users/quasar-analytics/following{/other_user}","gists_url":"https://api.github.com/users/quasar-analytics/gists{/gist_id}","starred_url":"https://api.github.com/users/quasar-analytics/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/quasar-analytics/subscriptions","organizations_url":"https://api.github.com/users/quasar-analytics/orgs","repos_url":"https://api.github.com/users/quasar-analytics/repos","events_url":"https://api.github.com/users/quasar-analytics/events{/privacy}","received_events_url":"https://api.github.com/users/quasar-analytics/received_events","type":"Organization","site_admin":false},"repo":{"id":16515058,"name":"quasar","full_name":"quasar-analytics/quasar","owner":{"login":"quasar-analytics","id":15095906,"avatar_url":"https://avatars1.githubusercontent.com/u/15095906?v=4","gravatar_id":"","url":"https://api.github.com/users/quasar-analytics","html_url":"https://github.com/quasar-analytics","followers_url":"https://api.github.com/users/quasar-analytics/followers","following_url":"https://api.github.com/users/quasar-analytics/following{/other_user}","gists_url":"https://api.github.com/users/quasar-analytics/gists{/gist_id}","starred_url":"https://api.github.com/users/quasar-analytics/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/quasar-analytics/subscriptions","organizations_url":"https://api.github.com/users/quasar-analytics/orgs","repos_url":"https://api.github.com/users/quasar-analytics/repos","events_url":"https://api.github.com/users/quasar-analytics/events{/privacy}","received_events_url":"https://api.github.com/users/quasar-analytics/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/quasar-analytics/quasar","description":"Quasar Analytics is a general-purpose compiler for translating data processing and analytics over semi-structured data into efficient plans that run 100% in the target infrastructure.","fork":false,"url":"https://api.github.com/repos/quasar-analytics/quasar","forks_url":"https://api.github.com/repos/quasar-analytics/quasar/forks","keys_url":"https://api.github.com/repos/quasar-analytics/quasar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/quasar-analytics/quasar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/quasar-analytics/quasar/teams","hooks_url":"https://api.github.com/repos/quasar-analytics/quasar/hooks","issue_events_url":"https://api.github.com/repos/quasar-analytics/quasar/issues/events{/number}","events_url":"https://api.github.com/repos/quasar-analytics/quasar/events","assignees_url":"https://api.github.com/repos/quasar-analytics/quasar/assignees{/user}","branches_url":"https://api.github.com/repos/quasar-analytics/quasar/branches{/branch}","tags_url":"https://api.github.com/repos/quasar-analytics/quasar/tags","blobs_url":"https://api.github.com/repos/quasar-analytics/quasar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/quasar-analytics/quasar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/quasar-analytics/quasar/git/refs{/sha}","trees_url":"https://api.github.com/repos/quasar-analytics/quasar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/quasar-analytics/quasar/statuses/{sha}","languages_url":"https://api.github.com/repos/quasar-analytics/quasar/languages","stargazers_url":"https://api.github.com/repos/quasar-analytics/quasar/stargazers","contributors_url":"https://api.github.com/repos/quasar-analytics/quasar/contributors","subscribers_url":"https://api.github.com/repos/quasar-analytics/quasar/subscribers","subscription_url":"https://api.github.com/repos/quasar-analytics/quasar/subscription","commits_url":"https://api.github.com/repos/quasar-analytics/quasar/commits{/sha}","git_commits_url":"https://api.github.com/repos/quasar-analytics/quasar/git/commits{/sha}","comments_url":"https://api.github.com/repos/quasar-analytics/quasar/comments{/number}","issue_comment_url":"https://api.github.com/repos/quasar-analytics/quasar/issues/comments{/number}","contents_url":"https://api.github.com/repos/quasar-analytics/quasar/contents/{+path}","compare_url":"https://api.github.com/repos/quasar-analytics/quasar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/quasar-analytics/quasar/merges","archive_url":"https://api.github.com/repos/quasar-analytics/quasar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/quasar-analytics/quasar/downloads","issues_url":"https://api.github.com/repos/quasar-analytics/quasar/issues{/number}","pulls_url":"https://api.github.com/repos/quasar-analytics/quasar/pulls{/number}","milestones_url":"https://api.github.com/repos/quasar-analytics/quasar/milestones{/number}","notifications_url":"https://api.github.com/repos/quasar-analytics/quasar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/quasar-analytics/quasar/labels{/name}","releases_url":"https://api.github.com/repos/quasar-analytics/quasar/releases{/id}","deployments_url":"https://api.github.com/repos/quasar-analytics/quasar/deployments","created_at":"2014-02-04T15:01:27Z","updated_at":"2017-11-20T13:36:11Z","pushed_at":"2017-11-21T13:48:00Z","git_url":"git://github.com/quasar-analytics/quasar.git","ssh_url":"git@github.com:quasar-analytics/quasar.git","clone_url":"https://github.com/quasar-analytics/quasar.git","svn_url":"https://github.com/quasar-analytics/quasar","homepage":"http://quasar-analytics.org","size":63151,"stargazers_count":570,"watchers_count":570,"language":"Scala","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":81,"mirror_url":null,"archived":false,"open_issues_count":413,"forks":81,"open_issues":413,"watchers":570,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137"},"html":{"href":"https://github.com/quasar-analytics/quasar/pull/3137"},"issue":{"href":"https://api.github.com/repos/quasar-analytics/quasar/issues/3137"},"comments":{"href":"https://api.github.com/repos/quasar-analytics/quasar/issues/3137/comments"},"review_comments":{"href":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137/comments"},"review_comment":{"href":"https://api.github.com/repos/quasar-analytics/quasar/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/quasar-analytics/quasar/pulls/3137/commits"},"statuses":{"href":"https://api.github.com/repos/quasar-analytics/quasar/statuses/d9e8ee0a9f76b68b17b0a3ffada14b4e575d2861"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"djspiewak"}}