RDatabaseSqlparserPositioncalculator   F
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 61
eloc 85
c 1
b 0
f 0
dl 0
loc 159
rs 3.52

4 Methods

Rating   Name   Duplication   Size   Complexity  
D lookForBaseExpression() 0 71 33
A setPositionsWithinSQL() 0 5 1
F findPositionWithinString() 0 56 24
A printPos() 0 15 3

How to fix   Complexity   

Complex Class

Complex classes like RDatabaseSqlparserPositioncalculator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RDatabaseSqlparserPositioncalculator, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package     Redcore
4
 * @subpackage  Exception
5
 *
6
 * @copyright   Copyright (C) 2008 - 2021 redWEB.dk. All rights reserved.
7
 * @license     GNU General Public License version 2 or later, see LICENSE.
8
 */
9
10
defined('JPATH_REDCORE') or die;
11
12
/**
13
 * This file implements the calculator for the position elements of
14
 * the output of the RDatabaseSqlparserSqlparser.
15
 *
16
 * Copyright (c) 2010-2012, Justin Swanhart
17
 * with contributions by André Rothe <[email protected], [email protected]>
18
 *
19
 * All rights reserved.
20
 *
21
 * Redistribution and use in source and binary forms, with or without modification,
22
 * are permitted provided that the following conditions are met:
23
 *
24
 *   * Redistributions of source code must retain the above copyright notice,
25
 *     this list of conditions and the following disclaimer.
26
 *   * Redistributions in binary form must reproduce the above copyright notice,
27
 *     this list of conditions and the following disclaimer in the documentation
28
 *     and/or other materials provided with the distribution.
29
 *
30
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
31
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
33
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
35
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
36
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
38
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
39
 * DAMAGE.
40
 */
41
42
/**
43
 * 
44
 * This class calculates the positions 
45
 * of base_expr within the original SQL statement.
46
 * 
47
 * @author arothe
48
 * 
49
 */
50
class RDatabaseSqlparserPositioncalculator extends RDatabaseSqlparserSqlparserutils {
51
52
    private static $allowedOnOperator = array("\t", "\n", "\r", " ", ",", "(", ")", "_", "'", "\"");
53
    private static $allowedOnOther = array("\t", "\n", "\r", " ", ",", "(", ")", "<", ">", "*", "+", "-", "/", "|",
54
                                           "&", "=", "!", ";");
55
56
    private function printPos($text, $sql, $charPos, $key, $parsed, $backtracking) {
0 ignored issues
show
Unused Code introduced by
The method printPos() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
57
        if (!isset($_ENV['DEBUG'])) {
58
            return;
59
        }
60
61
        $spaces = "";
62
        $caller = debug_backtrace();
63
        $i = 1;
64
        while ($caller[$i]['function'] === 'lookForBaseExpression') {
65
            $spaces .= "   ";
66
            $i++;
67
        }
68
        $holdem = substr($sql, 0, $charPos) . "^" . substr($sql, $charPos);
69
        echo $spaces . $text . " key:" . $key . "  parsed:" . $parsed . " back:" . serialize($backtracking) . " "
70
                . $holdem . "\n";
71
    }
72
73
    public function setPositionsWithinSQL($sql, $parsed) {
74
        $charPos = 0;
75
        $backtracking = array();
76
        $this->lookForBaseExpression($sql, $charPos, $parsed, 0, $backtracking);
77
        return $parsed;
78
    }
79
80
    private function findPositionWithinString($sql, $value, $expr_type) {
81
82
        $offset = 0;
83
        while (true) {
84
85
            $pos = strpos($sql, $value, $offset);
86
            if ($pos === false) {
87
                break;
88
            }
89
90
            $before = "";
91
            if ($pos > 0) {
92
                $before = $sql[$pos - 1];
93
            }
94
95
            $after = "";
96
            if (isset($sql[$pos + strlen($value)])) {
97
                $after = $sql[$pos + strlen($value)];
98
            }
99
100
	        // if we have an operator, it should be surrounded by
101
	        // whitespace, comma, parenthesis, digit or letter, end_of_string
102
	        // an operator should not be surrounded by another operator
103
104
            if ($expr_type === 'operator') {
105
106
                $ok = ($before === "" || in_array($before, self::$allowedOnOperator, true))
107
                        || (strtolower($before) >= 'a' && strtolower($before) <= 'z')
108
                        || ($before >= '0' && $before <= '9');
109
                $ok = $ok
110
                        && ($after === "" || in_array($after, self::$allowedOnOperator, true)
111
                                || (strtolower($after) >= 'a' && strtolower($after) <= 'z')
112
                                || ($after >= '0' && $after <= '9') || ($after === '?') || ($after === '@'));
113
114
                if (!$ok) {
115
                    $offset = $pos + 1;
116
                    continue;
117
                }
118
119
                break;
120
            }
121
122
	        // in all other cases we accept
123
	        // whitespace, comma, operators, parenthesis and end_of_string
124
125
            $ok = ($before === "" || in_array($before, self::$allowedOnOther, true));
126
            $ok = $ok && ($after === "" || in_array($after, self::$allowedOnOther, true));
127
128
            if ($ok) {
129
                break;
130
            }
131
132
            $offset = $pos + 1;
133
        }
134
135
        return $pos;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pos does not seem to be defined for all execution paths leading up to this point.
Loading history...
136
    }
137
138
    private function lookForBaseExpression($sql, &$charPos, &$parsed, $key, &$backtracking) {
139
        if (!is_numeric($key)) {
140
            if (($key === 'UNION' || $key === 'UNION ALL') || ($key === 'expr_type' && $parsed === 'expression')
141
                    || ($key === 'expr_type' && $parsed === 'subquery')
142
                    || ($key === 'expr_type' && $parsed === 'bracket_expression')
143
                    || ($key === 'expr_type' && $parsed === 'table_expression')
144
                    || ($key === 'expr_type' && $parsed === 'record')
145
                    || ($key === 'expr_type' && $parsed === 'in-list') || ($key === 'alias' && $parsed !== false)) {
146
	            // we hold the current position and come back after the next base_expr
147
	            // we do this, because the next base_expr contains the complete expression/subquery/record
148
	            // and we have to look into it too
149
                $backtracking[] = $charPos;
150
151
            } elseif (($key === 'ref_clause' || $key === 'columns') && $parsed !== false) {
152
              // we hold the current position and come back after n base_expr(s)
153
              // there is an array of sub-elements before (!) the base_expr clause of the current element
154
              // so we go through the sub-elements and must come at the end
155
                $backtracking[] = $charPos;
156
                $parsedCount = count($parsed);
157
                for ($i = 1; $i < $parsedCount; $i++) {
158
                    $backtracking[] = false; // backtracking only after n base_expr!
159
                }
160
            } elseif ($key === 'sub_tree' && $parsed !== false) {
161
              // we prevent wrong backtracking on subtrees (too much array_pop())
162
              // there is an array of sub-elements after(!) the base_expr clause of the current element
163
              // so we go through the sub-elements and must not come back at the end
164
                $parsedCount = count($parsed);
165
                for ($i = 1; $i < $parsedCount; $i++) {
166
                    $backtracking[] = false;
167
                }
168
            } else {
169
	            // move the current pos after the keyword
170
	            // SELECT, WHERE, INSERT etc.
171
                if (in_array($key, parent::$reserved)) {
172
                    $charPos = stripos($sql, $key, $charPos);
173
                    $charPos += strlen($key);
174
                }
175
            }
176
        }
177
178
        if (!is_array($parsed)) {
179
            return;
180
        }
181
182
        foreach ($parsed as $key => $value) {
0 ignored issues
show
introduced by
$key is overwriting one of the parameters of this function.
Loading history...
183
            if ($key === 'base_expr') {
184
185
	            // $this->printPos("0", $sql, $charPos, $key, $value, $backtracking);
186
187
                $subject = substr($sql, $charPos);
188
                $pos = $this->findPositionWithinString($subject, $value,
189
                        isset($parsed['expr_type']) ? $parsed['expr_type'] : 'alias');
190
                if ($pos === false) {
191
                    throw new Exception("cannot calculate position of " . $value . " within " . $subject, 5);
192
193
                }
194
195
                $parsed['position'] = $charPos + $pos;
196
                $charPos += $pos + strlen($value);
197
198
	            // $this->printPos("1", $sql, $charPos, $key, $value, $backtracking);
199
200
                $oldPos = array_pop($backtracking);
201
                if (isset($oldPos) && $oldPos !== false) {
202
                    $charPos = $oldPos;
203
                }
204
205
	            // $this->printPos("2", $sql, $charPos, $key, $value, $backtracking);
206
207
            } else {
208
                $this->lookForBaseExpression($sql, $charPos, $parsed[$key], $key, $backtracking);
209
            }
210
        }
211
    }
212
}
213