Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
18 / 18 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
QueryParser | |
100.00% |
18 / 18 |
|
100.00% |
4 / 4 |
10 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parseJoin | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
compileJoin | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
filterArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 |
1 | <?php declare(strict_types=1); |
2 | /** |
3 | * Query |
4 | * |
5 | * SQL Query Builder / Database Abstraction Layer |
6 | * |
7 | * PHP version 8.1 |
8 | * |
9 | * @package Query |
10 | * @author Timothy J. Warren <tim@timshome.page> |
11 | * @copyright 2012 - 2023 Timothy J. Warren |
12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License |
13 | * @link https://git.timshomepage.net/aviat/Query |
14 | * @version 4.0.0 |
15 | */ |
16 | |
17 | namespace Query; |
18 | |
19 | use Query\Drivers\DriverInterface; |
20 | |
21 | /** |
22 | * Utility Class to parse sql clauses for properly escaping identifiers |
23 | */ |
24 | class QueryParser |
25 | { |
26 | /** |
27 | * Regex patterns for various syntax components |
28 | */ |
29 | private array $matchPatterns = [ |
30 | 'function' => '([a-zA-Z0-9_]+\((.*?)\))', |
31 | 'identifier' => '([a-zA-Z0-9_-]+\.?)+', |
32 | 'operator' => '=|AND|&&?|~|\|\|?|\^|/|>=?|<=?|-|%|OR|\+|NOT|\!=?|<>|XOR', |
33 | ]; |
34 | |
35 | /** |
36 | * Regex matches |
37 | */ |
38 | public array $matches = [ |
39 | 'functions' => [], |
40 | 'identifiers' => [], |
41 | 'operators' => [], |
42 | 'combined' => [], |
43 | ]; |
44 | |
45 | /** |
46 | * Constructor/entry point into parser |
47 | */ |
48 | public function __construct(private readonly DriverInterface $db) |
49 | { |
50 | } |
51 | |
52 | /** |
53 | * Parser method for setting the parse string |
54 | * |
55 | * @return array[] |
56 | */ |
57 | public function parseJoin(string $sql): array |
58 | { |
59 | // Get sql clause components |
60 | preg_match_all('`' . $this->matchPatterns['function'] . '`', $sql, $this->matches['functions'], PREG_SET_ORDER); |
61 | preg_match_all('`' . $this->matchPatterns['identifier'] . '`', $sql, $this->matches['identifiers'], PREG_SET_ORDER); |
62 | preg_match_all('`' . $this->matchPatterns['operator'] . '`', $sql, $this->matches['operators'], PREG_SET_ORDER); |
63 | |
64 | // Get everything at once for ordering |
65 | $fullPattern = '`' . $this->matchPatterns['function'] . '+|' . $this->matchPatterns['identifier'] . '|(' . $this->matchPatterns['operator'] . ')+`i'; |
66 | preg_match_all($fullPattern, $sql, $this->matches['combined'], PREG_SET_ORDER); |
67 | |
68 | // Go through the matches, and get the most relevant matches |
69 | $this->matches = array_map([$this, 'filterArray'], $this->matches); |
70 | |
71 | return $this->matches; |
72 | } |
73 | |
74 | /** |
75 | * Compiles a join condition after parsing |
76 | */ |
77 | public function compileJoin(string $condition): string |
78 | { |
79 | $parts = $this->parseJoin($condition); |
80 | $count = is_countable($parts['identifiers']) ? count($parts['identifiers']) : 0; |
81 | |
82 | // Go through and quote the identifiers |
83 | for ($i=0; $i <= $count; $i++) |
84 | { |
85 | if (in_array($parts['combined'][$i], $parts['identifiers'], TRUE) && ! is_numeric($parts['combined'][$i])) |
86 | { |
87 | $parts['combined'][$i] = $this->db->quoteIdent($parts['combined'][$i]); |
88 | } |
89 | } |
90 | |
91 | return implode('', $parts['combined']); |
92 | } |
93 | |
94 | /** |
95 | * Returns a more useful match array |
96 | */ |
97 | protected function filterArray(array $array): array |
98 | { |
99 | $newArray = []; |
100 | |
101 | foreach ($array as $row) |
102 | { |
103 | $newArray[] = (is_array($row)) ? $row[0] : $row; |
104 | } |
105 | |
106 | return $newArray; |
107 | } |
108 | } |