/**
  * @param Token $token
  *
  * @return string
  */
 protected function renderToken(Token $token)
 {
     $type = $token->getType();
     // handle edge cases
     switch ($type) {
         case "open":
             return "(";
         case "close":
             return ")";
         case "group":
             return $this->renderGroupBy();
         case "sort":
             return $this->renderSort();
         case "function":
             /** @var Value $token */
             return $this->renderFunction($token);
     }
     if ($token instanceof Entity) {
         return $this->renderEntity($token);
     }
     if ($token instanceof Reference) {
         return $this->renderReference($token);
     }
     if ($token instanceof Value) {
         return $this->renderValue($token);
     }
     return strtoupper($type);
 }
 /**
  * queries with 2 or more includes require a series of select queries, unioned together, then sorted to produce an
  * array format that can be used to generate entities from
  *
  * @param Token $token
  *
  * @return string
  */
 protected function renderIncludeQuery(Token $token)
 {
     $collections = $this->query->getIncludes();
     $mainMetadata = $this->query->getEntityMetadata();
     $mainCollection = $mainMetadata->getCollection();
     $subqueryTemplateSql = "SELECT {{fieldList}} FROM " . $this->renderArbitraryReference($mainCollection);
     // parse the tokens for join conditions
     $joinConditions = [];
     $joinMap = [];
     while (!empty($token) && !in_array($token->getType(), ["group", "sort", "limit"])) {
         $subqueryTemplateSql .= " " . $this->renderToken($token);
         if ($token->getType() == "join") {
             /** @var Reference $ref */
             $ref = $this->query->getNextToken();
             $alias = $this->getReferenceAlias($ref);
             $subqueryTemplateSql .= " " . $this->renderToken($ref);
             $subqueryTemplateSql .= " " . $this->renderToken($this->query->getNextToken());
             // ON
             $subqueryTemplateSql .= " " . $this->renderToken($this->query->getNextToken());
             // (
             // write the join condition to a separate array, so we can replace it into the SQL when appropriate
             $level = 1;
             // counter to keep track of how deep we go into any closures
             $joinCondition = "";
             $joinCollectionsFound = [];
             while (($token = $this->query->getNextToken()) && !($token->getType() == "close" && $level == 1)) {
                 /** @var Token $token */
                 $joinCondition .= " " . $this->renderToken($token);
                 $type = $token->getType();
                 switch ($type) {
                     case "field":
                         /** @var Reference $token */
                         // save the collection from this reference
                         $collection = $this->getCollectionFromReference($token);
                         if (!empty($collection)) {
                             $joinCollectionsFound[] = $collection;
                         }
                         break;
                     case "open":
                         ++$level;
                         break;
                     case "close":
                         --$level;
                         break;
                 }
             }
             // if we found enough collections, create a mapping between them
             if (count($joinCollectionsFound) > 1) {
                 $this->mapJoins($joinCollectionsFound, $joinMap, $collections);
             }
             // save the join condition and write a placeholder to the template SQL
             $joinConditions[$alias] = $joinCondition;
             $subqueryTemplateSql .= "%{$alias}Condition%";
             $subqueryTemplateSql .= $this->renderToken($token);
             // )
         }
         $token = $this->query->getNextToken();
     }
     // add target entity to the collections array (as the first element to make sorting easier later)
     unset($collections[$mainCollection]);
     $collections = array_merge([$mainCollection => $mainMetadata], $collections);
     $primaryKeys = $this->getPrimaryKeys($collections);
     // recursively construct the join tree
     $joinTree = $this->constructJoinTree($joinMap, $mainCollection);
     // recursively parse the tree into join lists
     $joinLists = $this->parseJoinTree($joinTree, $collections);
     // create the SQL for each subquery
     $subqueries = [];
     foreach ($joinLists as $collectionList) {
         $templateJoinConditions = $joinConditions;
         $replacements = [];
         foreach ($templateJoinConditions as $collection => $condition) {
             // if this is a collection that were including and that isn't in the collection list, set it's join condition to "[primary key] = NULL"
             if (isset($collections[$collection]) && !in_array($collection, $collectionList)) {
                 $condition = "FALSE";
             }
             $replacements["%{$collection}Condition%"] = $condition;
         }
         $subqueries[] = strtr($subqueryTemplateSql, $replacements);
     }
     $sortData = $this->processSortFields($token, $collections, $mainCollection, $primaryKeys);
     $sortSql = empty($sortData["sort"]) ? "" : " ORDER BY " . implode(", ", $sortData["sort"]);
     $limitSort = empty($sortData["limitSort"]) ? "" : " ORDER BY " . implode(", ", $sortData["limitSort"]);
     // process limits
     $mainAliasedPk = $this->getSelectFieldAlias($mainCollection . "." . $primaryKeys[$mainCollection]);
     $limitSubquery = $this->processIncludeQueryLimit($sortData["nextToken"], $joinConditions, $collections, $subqueryTemplateSql, $limitSort, $mainAliasedPk);
     $fieldList = implode(", ", array_merge($this->fields, $sortData["additionalFields"]));
     $sql = "SELECT s.* FROM (" . implode(" UNION ", $subqueries) . ") s";
     $sql .= $limitSubquery;
     $sql = str_replace("{{fieldList}}", $fieldList, $sql);
     // add the sort SQL, but with the fields aliased to the "s" table
     $sql .= $sortSql;
     return $sql;
 }
Example #3
0
 /**
  * wraps the following expression in parentheses to isolate it
  * @param TokenSequencer|Token|mixed $content
  * @return $this
  */
 public function closure($content = null)
 {
     $this->addNewToSequence("open");
     if (!empty($content)) {
         if ($content instanceof TokenSequencer) {
             $this->mergeSequence($content->getSequence());
         } elseif ($content instanceof Token) {
             $this->addToSequence($content);
         } elseif (is_array($content)) {
             foreach ($content as $subContent) {
                 $this->addMixedContentToSequence($subContent);
             }
         } else {
             $this->val($content);
         }
     }
     $this->addNewToSequence("close");
     return $this;
 }