OracleSQLTranslator   C
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 358
Duplicated Lines 5.03 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 61
c 2
b 0
f 0
lcom 1
cbo 2
dl 18
loc 358
rs 6.018

23 Methods

Rating   Name   Duplication   Size   Complexity  
A processOrderByExpression() 0 18 4
A processSELECT() 0 22 3
A __construct() 0 6 1
A initGlobalVariables() 0 5 1
A dbgprint() 0 6 2
A preprint() 0 10 2
A processAlias() 0 10 2
A processDELETE() 0 8 2
A getColumnNameFor() 0 8 2
A getShortTableNameFor() 0 8 2
A processTable() 0 23 3
A processFROM() 0 6 1
B processTableExpression() 0 24 3
A getTableNameFromExpression() 9 9 2
A getColumnNameFromExpression() 9 9 2
A isCLOBColumnInDB() 0 8 1
C isCLOBColumn() 0 27 8
B processColRef() 0 30 6
A processFunctionOnSelect() 0 14 2
B correctColRefStatement() 0 22 5
B processSelectStatement() 0 26 4
A create() 0 16 2
A process() 0 13 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OracleSQLTranslator 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 OracleSQLTranslator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SQLParser;
4
5
/**
6
 * OracleSQLTranslator.
7
 *
8
 * A translator from MySQL dialect into Oracle dialect for Limesurvey
9
 * (http://www.limesurvey.org/)
10
 *
11
 * Copyright (c) 2012, André Rothe <[email protected], [email protected]>
12
 *
13
 * All rights reserved.
14
 *
15
 * Redistribution and use in source and binary forms, with or without modification,
16
 * are permitted provided that the following conditions are met:
17
 *
18
 *   * Redistributions of source code must retain the above copyright notice,
19
 *     this list of conditions and the following disclaimer.
20
 *   * Redistributions in binary form must reproduce the above copyright notice,
21
 *     this list of conditions and the following disclaimer in the documentation
22
 *     and/or other materials provided with the distribution.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
25
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
27
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
33
 * DAMAGE.
34
 */
35
36
//include_once(ROOT_PATH . 'classes/adodb/adodb.inc.php');
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
37
38
//$_ENV['DEBUG'] = 1;
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
39
40
class OracleSQLTranslator extends PHPSQLCreator
41
{
42
    private $con; # this is the database connection from LimeSurvey
43
    private $preventColumnRefs = array();
44
    private $allTables = array();
45
    const ASTERISK_ALIAS = '[#RePl#]';
46
47
    public function __construct($con)
48
    {
49
        parent::__construct();
50
        $this->con = $con;
51
        $this->initGlobalVariables();
52
    }
53
54
    private function initGlobalVariables()
55
    {
56
        $this->preventColumnRefs = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $preventColumnRefs.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
57
        $this->allTables = array();
58
    }
59
60
    public static function dbgprint($txt)
0 ignored issues
show
Coding Style introduced by
dbgprint uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
61
    {
62
        if (isset($_ENV['DEBUG'])) {
63
            print $txt;
64
        }
65
    }
66
67
    public static function preprint($s, $return = false)
68
    {
69
        $x = '<pre>';
70
        $x .= print_r($s, 1);
71
        $x .= '</pre>';
72
        if ($return) {
73
            return $x;
74
        }
75
        self::dbgprint($x."<br/>\n");
76
    }
77
78
    protected function processAlias($parsed)
79
    {
80
        if ($parsed === false) {
81
            return '';
82
        }
83
        # we don't need an AS between expression and alias
84
        $sql = ' '.$parsed['name'];
85
86
        return $sql;
87
    }
88
89
    protected function processDELETE($parsed)
90
    {
91
        if (count($parsed['TABLES']) > 1) {
92
            die('cannot translate delete statement into Oracle dialect, multiple tables are not allowed.');
0 ignored issues
show
Coding Style Compatibility introduced by
The method processDELETE() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
93
        }
94
95
        return 'DELETE';
96
    }
97
98
    public static function getColumnNameFor($column)
99
    {
100
        if (strtolower($column) === 'uid') {
101
            $column = 'uid_';
102
        }
103
        // TODO: add more here, if necessary
104
        return $column;
105
    }
106
107
    public static function getShortTableNameFor($table)
108
    {
109
        if (strtolower($table) === 'surveys_languagesettings') {
110
            $table = 'surveys_lngsettings';
111
        }
112
        // TODO: add more here, if necessary
113
        return $table;
114
    }
115
116
    protected function processTable($parsed, $index)
117
    {
118
        if ($parsed['expr_type'] !== 'table') {
119
            return '';
120
        }
121
122
        $sql = $this->getShortTableNameFor($parsed['table']);
123
        $alias = $this->processAlias($parsed['alias']);
124
        $sql .= $alias;
125
126
        if ($index !== 0) {
127
            $sql = $this->processJoin($parsed['join_type']).' '.$sql;
128
            $sql .= $this->processRefType($parsed['ref_type']);
129
            $sql .= $this->processRefClause($parsed['ref_clause']);
130
        }
131
132
        # store the table and its alias for later use
133
        $last = array_pop($this->allTables);
134
        $last['tables'][] = array('table' => $this->getShortTableNameFor($parsed['table']), 'alias' => trim($alias));
135
        $this->allTables[] = $last;
136
137
        return $sql;
138
    }
139
140
    protected function processFROM($parsed)
141
    {
142
        $this->allTables[] = array('tables' => array(), 'alias' => '');
143
144
        return parent::processFROM($parsed);
145
    }
146
147
    protected function processTableExpression($parsed, $index)
148
    {
149
        if ($parsed['expr_type'] !== 'table_expression') {
150
            return '';
151
        }
152
        $sql = substr($this->processFROM($parsed['sub_tree']), 5); // remove FROM keyword
153
        $sql = '('.$sql.')';
154
155
        $alias .= $this->processAlias($parsed['alias']);
0 ignored issues
show
Bug introduced by
The variable $alias does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
156
        $sql .= $alias;
157
158
        # store the tables-expression-alias for later use
159
        $last = array_pop($this->allTables);
160
        $last['alias'] = trim($alias);
161
        $this->allTables[] = $last;
162
163
        if ($index !== 0) {
164
            $sql = $this->processJoin($parsed['join_type']).' '.$sql;
165
            $sql .= $this->processRefType($parsed['ref_type']);
166
            $sql .= $this->processRefClause($parsed['ref_clause']);
167
        }
168
169
        return $sql;
170
    }
171
172 View Code Duplication
    private function getTableNameFromExpression($expr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
    {
174
        $pos = strpos($expr, '.');
175
        if ($pos === false) {
176
            $pos = -1;
177
        }
178
179
        return trim(substr($expr, 0, $pos + 1), '.');
180
    }
181
182 View Code Duplication
    private function getColumnNameFromExpression($expr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
    {
184
        $pos = strpos($expr, '.');
185
        if ($pos === false) {
186
            $pos = -1;
187
        }
188
189
        return substr($expr, $pos + 1);
190
    }
191
192
    private function isCLOBColumnInDB($table, $column)
193
    {
194
        $res = $this->con->GetOne(
195
                "SELECT count(*) FROM user_lobs WHERE table_name='".strtoupper($table)."' AND column_name='"
196
                        .strtoupper($column)."'");
197
198
        return ($res >= 1);
199
    }
200
201
    protected function isCLOBColumn($table, $column)
202
    {
203
        $tables = end($this->allTables);
204
205
        if ($table === '') {
206
            foreach ($tables['tables'] as $k => $v) {
207
                if ($this->isCLOBColumn($v['table'], $column)) {
208
                    return true;
209
                }
210
            }
211
212
            return false;
213
        }
214
215
        # check the aliases, $table cannot be empty
216
        foreach ($tables['tables'] as $k => $v) {
217
            if ((strtolower($v['alias']) === strtolower($table))
218
                    || (strtolower($tables['alias']) === strtolower($table))) {
219
                if ($this->isCLOBColumnInDB($v['table'], $column)) {
220
                    return true;
221
                }
222
            }
223
        }
224
225
        # it must be a valid table name
226
        return $this->isCLOBColumnInDB($table, $column);
227
    }
228
229
    protected function processOrderByExpression($parsed)
230
    {
231
        if ($parsed['type'] !== 'expression') {
232
            return '';
233
        }
234
235
        $table = $this->getTableNameFromExpression($parsed['base_expr']);
236
        $col = $this->getColumnNameFromExpression($parsed['base_expr']);
237
238
        $sql = ($table !== '' ? $table.'.' : '').$col;
239
240
        # check, if the column is a CLOB
241
        if ($this->isCLOBColumn($table, $col)) {
242
            $sql = 'cast(substr('.$sql.',1,200) as varchar2(200))';
243
        }
244
245
        return $sql.' '.$parsed['direction'];
246
    }
247
248
    protected function processColRef($parsed)
249
    {
250
        if ($parsed['expr_type'] !== 'colref') {
251
            return '';
252
        }
253
254
        $table = $this->getTableNameFromExpression($parsed['base_expr']);
255
        $col = $this->getColumnNameFromexpression($parsed['base_expr']);
256
257
        # we have to change the column name, if the column is uid
258
        # we have to change the tablereference, if the tablename is too long
259
        $col = $this->getColumnNameFor($col);
260
        $table = $this->getShortTableNameFor($table);
261
262
        # if we have * as colref, we cannot use other columns
263
        # we have to add alias.* if we know all table aliases
264
        if (($table === '') && ($col === '*')) {
265
            array_pop($this->preventColumnRefs);
266
            $this->preventColumnRefs[] = true;
267
268
            return ASTERISK_ALIAS; # this is the position, we have to replace later
269
        }
270
271
        $alias = '';
272
        if (isset($parsed['alias'])) {
273
            $alias = $this->processAlias($parsed['alias']);
274
        }
275
276
        return (($table !== '') ? ($table.'.'.$col) : $col).$alias;
277
    }
278
279
    protected function processFunctionOnSelect($parsed)
280
    {
281
        $old = end($this->preventColumnRefs);
282
        $sql = $this->processFunction($parsed);
283
284
        if ($old !== end($this->preventColumnRefs)) {
285
            # prevents wrong handling of count(*)
286
            array_pop($this->preventColumnRefs);
287
            $this->preventColumnRefs[] = $old;
288
            $sql = str_replace(ASTERISK_ALIAS, '*', $sql);
289
        }
290
291
        return $sql;
292
    }
293
294
    protected function processSELECT($parsed)
295
    {
296
        $this->preventColumnRefs[] = false;
297
298
        $sql = '';
299
        foreach ($parsed as $k => $v) {
300
            $len = strlen($sql);
301
            $sql .= $this->processColRef($v);
302
            $sql .= $this->processSelectExpression($v);
303
            $sql .= $this->processFunctionOnSelect($v);
304
            $sql .= $this->processConstant($v);
305
306
            if ($len == strlen($sql)) {
307
                $this->stop('SELECT', $k, $v, 'expr_type');
0 ignored issues
show
Bug introduced by
The method stop() does not seem to exist on object<SQLParser\OracleSQLTranslator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
308
            }
309
310
            $sql .= ',';
311
        }
312
        $sql = substr($sql, 0, -1);
313
314
        return 'SELECT '.$sql;
315
    }
316
317
    private function correctColRefStatement($sql)
318
    {
319
        $alias = '';
320
        $tables = end($this->allTables);
321
322
        # should we correct the selection list?
323
        if (array_pop($this->preventColumnRefs)) {
324
325
            # do we have a table-expression alias?
326
            if ($tables['alias'] !== '') {
327
                $alias = $tables['alias'].'.*';
328
            } else {
329
                foreach ($tables['tables'] as $k => $v) {
330
                    $alias .= ($v['alias'] === '' ? $v['table'] : $v['alias']).'.*,';
331
                }
332
                $alias = substr($alias, 0, -1);
333
            }
334
            $sql = str_replace(ASTERISK_ALIAS, $alias, $sql);
335
        }
336
337
        return $sql;
338
    }
339
340
    protected function processSelectStatement($parsed)
341
    {
342
        $sql = $this->processSELECT($parsed['SELECT']);
343
        $from = $this->processFROM($parsed['FROM']);
344
345
        # correct * references with tablealias.*
346
        # this must be called after processFROM(), because we need the table information
347
        $sql = $this->correctColRefStatement($sql).' '.$from;
348
349
        if (isset($parsed['WHERE'])) {
350
            $sql .= ' '.$this->processWHERE($parsed['WHERE']);
351
        }
352
        if (isset($parsed['GROUP'])) {
353
            $sql .= ' '.$this->processGROUP($parsed['GROUP']);
354
        }
355
        if (isset($parsed['ORDER'])) {
356
            $sql .= ' '.$this->processORDER($parsed['ORDER']);
357
        }
358
359
        # select finished, we remove its tables
360
        #  FIXME: we should add it to the previous tablelist with the
361
        #  global alias, if such one exists
362
        array_pop($this->allTables);
363
364
        return $sql;
365
    }
366
367
    public function create($parsed)
368
    {
369
        $k = key($parsed);
370
        switch ($k) {
371
        case 'USE':
372
        # this statement is not an Oracle statement
373
            $this->created = '';
0 ignored issues
show
Bug introduced by
The property created does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
374
            break;
375
376
        default:
377
            $this->created = parent::create($parsed);
378
            break;
379
        }
380
381
        return $this->created;
382
    }
383
384
    public function process($sql)
385
    {
386
        self::dbgprint($sql.'<br/>');
387
388
        $this->initGlobalVariables();
389
        $parser = new SQLParser($sql);
390
        self::preprint($parser->parsed);
0 ignored issues
show
Bug introduced by
The property parsed does not seem to exist in SQLParser\SQLParser.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
391
392
        $sql = $this->create($parser->parsed);
393
        self::dbgprint($sql.'<br/>');
394
395
        return $sql;
396
    }
397
}
398
399
//$translator = new OracleSQLTranslator(false);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
400
//$translator->process("SELECT q.qid, question, gid FROM questions as q WHERE (select count(*) from answers as a where a.qid=q.qid and scale_id=0)=0 and sid=11929 AND type IN ('F', 'H', 'W', 'Z', '1') and q.parent_qid=0");
401
//$translator->process("SELECT *, (SELECT a from xyz WHERE b>1) haha, (SELECT *, b from zks,abc WHERE d=1) hoho FROM blubb d, blibb c WHERE d.col = c.col");
402
403