lexer = $lexer; $this->parser = $parser; $this->pageSqlString = $sql; if ($pageContext == null) { $this->requestedPage = MarkupPath::createPageFromPathObject(ExecutionContext::getActualOrCreateFromEnv()->getContextPath()); } else { $this->requestedPage = $pageContext; } } /** * Leaf node * @param TerminalNode $node */ public function visitTerminal(TerminalNode $node): void { $type = $node->getSymbol()->getType(); $text = $node->getText(); switch ($type) { case PageSqlParser::SELECT: $this->physicalSql .= "select\n\t*\n"; /** * The from select is optional * Check if it's there */ $parent = $node->getParent(); for ($i = 0; $i < $parent->getChildCount(); $i++) { $child = $parent->getChild($i); if ($child instanceof ParserRuleContext) { /** * @var ParserRuleContext $child */ if ($child->getRuleIndex() === PageSqlParser::RULE_tables) { return; } } } $this->physicalSql .= "from\n\tpages\n"; break; case PageSqlParser::SqlName: switch ($this->ruleState) { case PageSqlParser::RULE_predicates: if (substr($this->physicalSql, -1) === "\n") { $this->physicalSql .= "\t"; } // variable name $variableName = strtolower($text); if ($variableName === DatabasePageRow::IS_HOME_COLUMN) { /** * Deprecation of is_home for is_index */ $variableName = DatabasePageRow::IS_INDEX_COLUMN; } $this->actualPredicateColumn = $variableName; if ($this->tableName === self::BACKLINKS) { $variableName = "p." . $variableName; } if ($variableName === self::DEPTH) { $variableName = "level"; } $this->physicalSql .= "{$variableName} "; break; case PageSqlParser::RULE_orderBys: $variableName = strtolower($text); if ($this->tableName === self::BACKLINKS) { $variableName = "p." . $variableName; } $this->physicalSql .= "\t{$variableName} "; break; case PageSqlParser::RULE_columns: $this->columns[] = $text; break; } break; case PageSqlParser::EQUAL: case PageSqlParser::LIKE: case PageSqlParser::GLOB: case PageSqlParser::LESS_THAN_OR_EQUAL: case PageSqlParser::LESS_THAN: case PageSqlParser::GREATER_THAN: case PageSqlParser::GREATER_THAN_OR_EQUAL: case PageSqlParser::NOT_EQUAL: switch ($this->ruleState) { case PageSqlParser::RULE_predicates: $this->physicalSql .= "{$text} "; } break; case PageSqlParser::RANDOM: $this->physicalSql .= "\trandom()"; break; case PageSqlParser::StringLiteral: switch ($this->ruleState) { case PageSqlParser::RULE_predicates: // Parameters if ( ($text[0] === "'" and $text[strlen($text) - 1] === "'") || ($text[0] === '"' and $text[strlen($text) - 1] === '"')) { $quote = $text[0]; $text = substr($text, 1, strlen($text) - 2); $text = str_replace("$quote$quote", "$quote", $text); } $this->parameters[] = $text; $this->physicalSql .= "?"; break; } break; case PageSqlParser:: AND: case PageSqlParser:: OR: if ($this->ruleState === PageSqlParser::RULE_predicates) { $this->physicalSql .= " {$text}\n"; } return; case PageSqlParser::LIMIT: case PageSqlParser::NOT: $this->physicalSql .= "{$text} "; return; case PageSqlParser::DESC: case PageSqlParser::LPAREN: case PageSqlParser::RPAREN: case PageSqlParser::ASC: $this->physicalSql .= "{$text}"; break; case PageSqlParser::COMMA: switch ($this->ruleState) { case PageSqlParser::RULE_columns: return; case PageSqlParser::RULE_orderBys: $this->physicalSql .= "{$text}\n"; return; default: $this->physicalSql .= "{$text}"; return; } case PageSqlParser::ESCAPE: $this->physicalSql .= " {$text} "; return; case PageSqlParser::Number: switch ($this->ruleState) { case PageSqlParser::RULE_limit: $this->physicalSql .= "{$text}"; return; case PageSqlParser::RULE_predicates: switch ($this->actualPredicateColumn) { case self::DEPTH: if ($this->requestedPage !== null) { $level = PageLevel::createForPage($this->requestedPage)->getValue(); try { $predicateValue = DataType::toInteger($text); } catch (ExceptionCompile $e) { // should not happen due to the parsing but yeah LogUtility::msg("The value of the depth attribute ($text) is not an integer", LogUtility::LVL_MSG_ERROR, self::CANONICAL); $predicateValue = 0; } $this->parameters[] = $predicateValue + $level; } else { LogUtility::msg("The requested page is unknown and is mandatory with the depth attribute", LogUtility::LVL_MSG_ERROR, self::CANONICAL); $this->parameters[] = $text; } break; default: try { if (strpos($text, ".") !== false) { $this->parameters[] = DataType::toFloat($text); } else { $this->parameters[] = DataType::toInteger($text); } } catch (ExceptionBadArgument $e) { LogUtility::error("The value of the column $this->actualPredicateColumn ($text) could not be transformed as a number. Error: {$e->getMessage()}", self::CANONICAL); $this->parameters[] = $text; } break; } $this->physicalSql .= "?"; return; default: $this->physicalSql .= "{$text} "; return; } default: // We do nothing because the token may have been printed at a higher level such as order by } } public function visitErrorNode(ErrorNode $node): void { $charPosition = $node->getSymbol()->getStartIndex(); $textMakingTheError = $node->getText(); // $this->lexer->getText(); $position = "at position: $charPosition"; if ($charPosition != 0) { $position .= ", in `" . substr($this->pageSqlString, $charPosition, -1) . "`"; } $message = "PageSql Parsing Error: The token `$textMakingTheError` was unexpected ($position)."; throw new \RuntimeException($message); } /** * * Parent Node * * On each node, enterRule is called before recursively walking down into child nodes, * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up. * Parameters: * @param ParserRuleContext $ctx */ public function enterEveryRule(ParserRuleContext $ctx): void { $ruleIndex = $ctx->getRuleIndex(); if (in_array($ruleIndex, self::STATE_VALUES)) { $this->ruleState = $ruleIndex; } switch ($ruleIndex) { case PageSqlParser::RULE_orderBys: $this->physicalSql .= "order by\n"; break; case PageSqlParser::RULE_tables: $this->physicalSql .= "from\n"; break; case PageSqlParser::RULE_predicates: /** * Backlinks/Descendant query adds already a where clause */ switch ($this->tableName) { case self::BACKLINKS: $this->physicalSql .= "\tand "; break; case self::DESCENDANTS: $this->physicalSql .= "\tand ("; break; default: $this->physicalSql .= "where\n"; break; } break; case PageSqlParser::RULE_functionNames: // Print the function name $this->physicalSql .= $ctx->getText(); break; case PageSqlParser::RULE_tableNames: // Print the table name $tableName = strtolower($ctx->getText()); $this->tableName = $tableName; switch ($tableName) { case self::BACKLINKS: $tableName = <<requestedPage !== null) { $this->parameters[] = $this->requestedPage->getPathObject()->toAbsoluteId(); } else { LogUtility::msg("The page is unknown. A Page SQL with backlinks should be asked within a page request scope.", LogUtility::LVL_MSG_ERROR, PageSql::CANONICAL); $this->parameters[] = "unknown page"; } break; case self::DESCENDANTS: if ($this->requestedPage !== null) { if (!$this->requestedPage->isIndexPage()) { LogUtility::warning("Descendants should be asked from an index page.", PageSql::CANONICAL); } $path = $this->requestedPage->getPathObject(); $this->parameters[] = $path->toAbsoluteId(); try { $likePredicatequery = $path->getParent()->resolve("%")->toAbsoluteId(); } catch (ExceptionNotFound $e) { // root $likePredicatequery = "%"; } $this->parameters[] = $likePredicatequery; $level = PageLevel::createForPage($this->requestedPage)->getValue(); $this->parameters[] = $level; } else { LogUtility::msg("The page is unknown. A Page SQL with a depth attribute should be asked within a page request scope. The start depth has been set to 0", LogUtility::LVL_MSG_ERROR, PageSql::CANONICAL); $this->parameters[] = ""; $this->parameters[] = ""; $this->parameters[] = ""; } $tableName = "\tpages\nwhere\n\tpath != ?\n\tand path like ?\n\tand level >= ?\n"; break; default: $tableName = "\t$tableName\n"; break; } $this->physicalSql .= $tableName; break; } } /** * * Parent Node * * On each node, {@link PageSqlTreeListener::enterEveryRule()} is called before recursively walking down into child nodes, * then {@link PageSqlTreeListener::exitEveryRule()} is called after the recursive call to wind up. * @param ParserRuleContext $ctx */ public function exitEveryRule(ParserRuleContext $ctx): void { $ruleIndex = $ctx->getRuleIndex(); switch ($ruleIndex) { case PageSqlParser::RULE_orderBys: $this->physicalSql .= "\n"; break; case PageSqlParser::RULE_predicates: if ($this->tableName == self::DESCENDANTS) { $this->physicalSql .= ")"; } $this->physicalSql .= "\n"; break; } } public function getParameters(): array { return $this->parameters; } public function getColumns(): array { return $this->columns; } public function getTable(): ?string { return $this->tableName; } /** * For documentation * @param ParserRuleContext $ctx * @return string */ private function getRuleName(ParserRuleContext $ctx): string { $ruleNames = $this->parser->getRuleNames(); return $ruleNames[$ctx->getRuleIndex()]; } /** * For documentation * @param TerminalNode $node * @return string|null */ private function getTokenName(TerminalNode $node) { $token = $node->getSymbol(); return $this->lexer->getVocabulary()->getSymbolicName($token->getType()); } public function getPhysicalSql(): string { return $this->physicalSql; } }