Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
QueryParser
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
4 / 4
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseJoin
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 compileJoin
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 filterArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
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
17namespace Query;
18
19use Query\Drivers\DriverInterface;
20
21/**
22 * Utility Class to parse sql clauses for properly escaping identifiers
23 */
24class 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}