Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v5] Feature / Perfomance: compiled queries #6788

Draft
wants to merge 18 commits into
base: v5/develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions src/Query/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Kirby\Query;

use Closure;
use Exception;
use Kirby\Cms\App;
use Kirby\Cms\Collection;
use Kirby\Cms\File;
Expand All @@ -11,6 +12,8 @@
use Kirby\Cms\User;
use Kirby\Image\QrCode;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Query\Runners\Interpreted;
use Kirby\Toolkit\Query\Runners\Transpiled;

/**
* The Query class can be used to query arrays and objects,
Expand Down Expand Up @@ -66,18 +69,8 @@ public function intercept(mixed $result): mixed
return $result;
}

/**
* Returns the query result if anything
* can be found, otherwise returns null
*
* @throws \Kirby\Exception\BadMethodCallException If an invalid method is accessed by the query
*/
public function resolve(array|object $data = []): mixed
private function resolve_legacy(array|object $data = []): mixed
{
if (empty($this->query) === true) {
return $data;
}

// merge data with default entries
if (is_array($data) === true) {
$data = [...static::$entries, ...$data];
Expand All @@ -99,6 +92,35 @@ public function resolve(array|object $data = []): mixed

// loop through all segments to resolve query
return Expression::factory($this->query, $this)->resolve($data);

}

/**
* Returns the query result if anything
* can be found, otherwise returns null
*
* @throws \Kirby\Exception\BadMethodCallException If an invalid method is accessed by the query
*/
public function resolve(array|object $data = []): mixed
{
if (empty($this->query) === true) {
return $data;
}

$mode = App::instance()->option('query.runner', 'interpreted');

if ($mode === 'legacy') {
return $this->resolve_legacy($data);
}

$runnerClass = match($mode) {
'transpiled' => Transpiled::class,
'interpreted' => Interpreted::class,
default => throw new Exception('Invalid query runner')
};

$runner = new $runnerClass(static::$entries, $this->intercept(...));
return $runner->run($this->query, (array)$data);
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/Toolkit/Query/AST/ArgumentListNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class ArgumentListNode extends Node
{
public function __construct(
public array $arguments,
) {
}
}
11 changes: 11 additions & 0 deletions src/Toolkit/Query/AST/ArrayListNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class ArrayListNode extends Node
{
public function __construct(
public array $elements,
) {
}
}
15 changes: 15 additions & 0 deletions src/Toolkit/Query/AST/ClosureNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class ClosureNode extends Node
{
/**
* @param string[] $arguments The arguments names
*/
public function __construct(
public array $arguments,
public Node $body,
) {
}
}
12 changes: 12 additions & 0 deletions src/Toolkit/Query/AST/CoalesceNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class CoalesceNode extends Node
{
public function __construct(
public Node $left,
public Node $right,
) {
}
}
20 changes: 20 additions & 0 deletions src/Toolkit/Query/AST/GlobalFunctionNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class GlobalFunctionNode extends IdentifierNode
{
public function __construct(
public string $name,
public ArgumentListNode $arguments,
) {
}

/**
* Replace escaped dots with real dots
*/
public function name(): string
{
return str_replace('\.', '.', $this->name);
}
}
14 changes: 14 additions & 0 deletions src/Toolkit/Query/AST/IdentifierNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Kirby\Toolkit\Query\AST;

abstract class IdentifierNode extends Node
{
/**
* Replaces the escaped identifier with the actual identifier
*/
public static function unescape(string $name): string
{
return stripslashes($name);
}
}
11 changes: 11 additions & 0 deletions src/Toolkit/Query/AST/LiteralNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class LiteralNode extends Node
{
public function __construct(
public mixed $value,
) {
}
}
26 changes: 26 additions & 0 deletions src/Toolkit/Query/AST/MemberAccessNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class MemberAccessNode extends IdentifierNode
{
public function __construct(
public Node $object,
public string|int $member,
public ArgumentListNode|null $arguments = null,
public bool $nullSafe = false,
) {
}

/**
* Returns the member name and replaces escaped dots with real dots if it's a string
*/
public function member(): string|int
{
if (is_string($this->member)) {
return self::unescape($this->member);
}
return $this->member;

}
}
13 changes: 13 additions & 0 deletions src/Toolkit/Query/AST/Node.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Kirby\Toolkit\Query\AST;

use Kirby\Toolkit\Query\Visitor;

class Node
{
public function accept(Visitor $visitor)
{
return $visitor->visitNode($this);
}
}
14 changes: 14 additions & 0 deletions src/Toolkit/Query/AST/TernaryNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class TernaryNode extends Node
{
public function __construct(
public Node $condition,
public Node|null $trueBranch,
public Node $falseBranch,
public bool $trueBranchIsDefault = false,
) {
}
}
19 changes: 19 additions & 0 deletions src/Toolkit/Query/AST/VariableNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Kirby\Toolkit\Query\AST;

class VariableNode extends IdentifierNode
{
public function __construct(
public string $name,
) {
}

/**
* Replaces escaped dots with real dots
*/
public function name(): string
{
return self::unescape($this->name);
}
}
90 changes: 90 additions & 0 deletions src/Toolkit/Query/BaseParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Kirby\Toolkit\Query;

use Exception;
use Iterator;

abstract class BaseParser
{
protected Token|null $previous;
protected Token $current;

/**
* @var Iterator<Token>
*/
protected Iterator $tokens;


public function __construct(
Tokenizer|Iterator $source,
) {
if ($source instanceof Tokenizer) {
$source = $source->tokenize();
}

$this->tokens = $source;
$first = $this->tokens->current();

if ($first === null) {
throw new Exception('No tokens found.');
}

$this->current = $first;
}

protected function consume(TokenType $type, string $message): Token
{
if ($this->check($type) === true) {
return $this->advance();
}

throw new Exception($message);
}

protected function check(TokenType $type): bool
{
if ($this->isAtEnd() === true) {
return false;
}

return $this->current->type === $type;
}

protected function advance(): Token|null
{
if ($this->isAtEnd() === false) {
$this->previous = $this->current;
$this->tokens->next();
$this->current = $this->tokens->current();
}

return $this->previous;
}

protected function isAtEnd(): bool
{
return $this->current->type === TokenType::T_EOF;
}


protected function match(TokenType $type): Token|false
{
if ($this->check($type) === true) {
return $this->advance();
}

return false;
}

protected function matchAny(array $types): Token|false
{
foreach ($types as $type) {
if ($this->check($type) === true) {
return $this->advance();
}
}

return false;
}
}
Loading
Loading