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