Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.62% covered (success)
97.62%
41 / 42
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Expression
97.62% covered (success)
97.62%
41 / 42
75.00% covered (warning)
75.00%
3 / 4
34
0.00% covered (danger)
0.00%
0 / 1
 getOperator
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
18
 getOperand
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
10
 localeIds
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 translateKeyword
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Finder\Output;
6
7use Celema\Quma\Database;
8use Cosray\Context;
9use Cosray\Exception\ParserException;
10use Cosray\Exception\ParserOutputException;
11use Cosray\Finder\CompilesField;
12use Cosray\Finder\Input\Token;
13use Cosray\Finder\Input\TokenType;
14
15abstract readonly class Expression implements Output
16{
17    use CompilesField;
18
19    protected function getOperator(TokenType $type): string
20    {
21        return match ($type) {
22            TokenType::LeftParen => '(',
23            TokenType::RightParen => ')',
24            TokenType::Equal => '=',
25            TokenType::Greater => '>',
26            TokenType::GreaterEqual => '>=',
27            TokenType::Less => '<',
28            TokenType::LessEqual => '<=',
29            TokenType::Like => 'LIKE',
30            TokenType::ILike => 'ILIKE',
31            TokenType::Unequal => '!=',
32            TokenType::Unlike => 'NOT LIKE',
33            TokenType::IUnlike => 'NOT ILIKE',
34            TokenType::And => 'AND',
35            TokenType::Or => 'OR',
36            TokenType::In => 'IN',
37            TokenType::NotIn => 'NOT IN',
38            default => throw new ParserException('Invalid expression operator: ' . $type->name),
39        };
40    }
41
42    /** @param array<string, string> $builtins */
43    protected function getOperand(
44        #[\SensitiveParameter]
45        Token $token,
46        Database $db,
47        array $builtins,
48        ?Context $context = null,
49    ): string {
50        return match ($token->type) {
51            TokenType::Boolean => strtolower($token->lexeme),
52            TokenType::Field => $this->compileField(
53                $token->lexeme,
54                'n.content',
55                localeIds: $this->localeIds($context),
56            ),
57            TokenType::Builtin => $builtins[$token->lexeme],
58            TokenType::Keyword => $this->translateKeyword($token->lexeme),
59            TokenType::Null => 'NULL',
60            TokenType::Number => $token->lexeme,
61            TokenType::String => $db->quote($token->lexeme),
62            TokenType::List => $token->lexeme,
63            default => throw new ParserOutputException($token, 'Invalid operand.'),
64        };
65    }
66
67    /** @return list<string> */
68    private function localeIds(?Context $context): array
69    {
70        if (!$context) {
71            return ['zxx'];
72        }
73
74        $ids = [];
75        $locale = $context->locale();
76
77        while ($locale) {
78            $ids[] = $locale->id;
79            $locale = $locale->fallback();
80        }
81
82        return $ids;
83    }
84
85    protected function translateKeyword(string $keyword): string
86    {
87        return match ($keyword) {
88            'now' => 'NOW()',
89            default => throw new ParserException("Unknown keyword '{$keyword}'"),
90        };
91    }
92}