Database_Pdosqlite_Driver::compile_select()   F
last analyzed

Complexity

Conditions 11
Paths 512

Size

Total Lines 44
Code Lines 25

Duplication

Lines 44
Ratio 100 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 11
eloc 25
c 2
b 1
f 0
nc 512
nop 1
dl 44
loc 44
rs 3.1764

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php defined('SYSPATH') or die('No direct access allowed.');
2
/*
3
 * Class: Database_PdoSqlite_Driver
4
 *  Provides specific database items for Sqlite.
5
 *
6
 * Connection string should be, eg: "pdosqlite://path/to/database.db"
7
 *
8
 * Version 1.0 alpha
9
 *  author    - Doutu, updated by gregmac
10
 *  copyright - (c) BSD
11
 *  license   - <no>
12
 */
13
14
class Database_Pdosqlite_Driver extends Database_Driver
15
{
16
17
    // Database connection link
18
    protected $link;
19
    protected $db_config;
20
21
    /*
22
     * Constructor: __construct
23
     *  Sets up the config for the class.
24
     *
25
     * Parameters:
26
     *  config - database configuration
27
     *
28
     */
29
    public function __construct($config)
30
    {
31
        $this->db_config = $config;
32
33
        Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized');
34
    }
35
36
    public function connect()
37
    {
38
        // Import the connect variables
39
        extract($this->db_config['connection']);
40
41
        try {
42
            $this->link = new PDO('sqlite:'.$socket.$database, $user, $pass,
43
                array(PDO::ATTR_PERSISTENT => $this->db_config['persistent']));
44
45
            $this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
46
            //$this->link->query('PRAGMA count_changes=1;');
47
48
            if ($charset = $this->db_config['character_set']) {
49
                $this->set_charset($charset);
50
            }
51
        } catch (PDOException $e) {
52
            throw new Kohana_Database_Exception('database.error', $e->getMessage());
53
        }
54
55
        // Clear password after successful connect
56
        $this->db_config['connection']['pass'] = null;
57
58
        return $this->link;
59
    }
60
61
    /**
62
     * @param string $sql
63
     */
64
    public function query($sql)
65
    {
66
        try {
67
            $sth = $this->link->prepare($sql);
68
        } catch (PDOException $e) {
69
            throw new Kohana_Database_Exception('database.error', $e->getMessage());
70
        }
71
        return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql);
72
    }
73
74
    public function set_charset($charset)
75
    {
76
        $this->link->query('PRAGMA encoding = '.$this->escape_str($charset));
77
    }
78
79 View Code Duplication
    public function escape_table($table)
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...
80
    {
81
        if (! $this->db_config['escape']) {
82
            return $table;
83
        }
84
85
        return '`'.str_replace('.', '`.`', $table).'`';
86
    }
87
88 View Code Duplication
    public function escape_column($column)
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...
89
    {
90
        if (! $this->db_config['escape']) {
91
            return $column;
92
        }
93
94
        if ($column == '*') {
95
            return $column;
96
        }
97
98
        // This matches any functions we support to SELECT.
99
        if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) {
100
            if (count($matches) == 3) {
101
                return $matches[1].'('.$this->escape_column($matches[2]).')';
102
            } elseif (count($matches) == 5) {
103
                return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
104
            }
105
        }
106
107
        // This matches any modifiers we support to SELECT.
108
        if (! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) {
109
            if (stripos($column, ' AS ') !== false) {
110
                // Force 'AS' to uppercase
111
                $column = str_ireplace(' AS ', ' AS ', $column);
112
113
                // Runs escape_column on both sides of an AS statement
114
                $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
115
116
                // Re-create the AS statement
117
                return implode(' AS ', $column);
118
            }
119
120
            return preg_replace('/[^.*]+/', '`$0`', $column);
121
        }
122
123
        $parts = explode(' ', $column);
124
        $column = '';
125
126
        for ($i = 0, $c = count($parts); $i < $c; $i++) {
127
            // The column is always last
128
            if ($i == ($c - 1)) {
129
                $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
130
            } else { // otherwise, it's a modifier
131
                $column .= $parts[$i].' ';
132
            }
133
        }
134
        return $column;
135
    }
136
137
    public function limit($limit, $offset = 0)
138
    {
139
        return 'LIMIT '.$offset.', '.$limit;
140
    }
141
142 View Code Duplication
    public function compile_select($database)
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...
143
    {
144
        $sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT ';
145
        $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
146
147
        if (count($database['from']) > 0) {
148
            $sql .= "\nFROM ";
149
            $sql .= implode(', ', $database['from']);
150
        }
151
152
        if (count($database['join']) > 0) {
153
            foreach ($database['join'] as $join) {
154
                $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
155
            }
156
        }
157
158
        if (count($database['where']) > 0) {
159
            $sql .= "\nWHERE ";
160
        }
161
162
        $sql .= implode("\n", $database['where']);
163
164
        if (count($database['groupby']) > 0) {
165
            $sql .= "\nGROUP BY ";
166
            $sql .= implode(', ', $database['groupby']);
167
        }
168
169
        if (count($database['having']) > 0) {
170
            $sql .= "\nHAVING ";
171
            $sql .= implode("\n", $database['having']);
172
        }
173
174
        if (count($database['orderby']) > 0) {
175
            $sql .= "\nORDER BY ";
176
            $sql .= implode(', ', $database['orderby']);
177
        }
178
179
        if (is_numeric($database['limit'])) {
180
            $sql .= "\n";
181
            $sql .= $this->limit($database['limit'], $database['offset']);
182
        }
183
184
        return $sql;
185
    }
186
187
    public function escape_str($str)
188
    {
189
        if (! $this->db_config['escape']) {
190
            return $str;
191
        }
192
193
        if (function_exists('sqlite_escape_string')) {
194
            $res = sqlite_escape_string($str);
195
        } else {
196
            $res = str_replace("'", "''", $str);
197
        }
198
        return $res;
199
    }
200
201
    public function list_tables()
202
    {
203
        $sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;";
204
        try {
205
            $result = $this->query($sql)->result(false, PDO::FETCH_ASSOC);
206
            $tables = array();
207
            foreach ($result as $row) {
208
                $tables[] = current($row);
209
            }
210
        } catch (PDOException $e) {
211
            throw new Kohana_Database_Exception('database.error', $e->getMessage());
212
        }
213
        return $tables;
214
    }
215
216
    public function show_error()
217
    {
218
        $err = $this->link->errorInfo();
219
        return isset($err[2]) ? $err[2] : 'Unknown error!';
220
    }
221
222
    public function list_fields($table, $query = false)
223
    {
224
        static $tables;
225
        if (is_object($query)) {
226
            if (empty($tables[$table])) {
227
                $tables[$table] = array();
228
229
                foreach ($query->result() as $row) {
230
                    $tables[$table][] = $row->name;
231
                }
232
            }
233
234
            return $tables[$table];
235
        } else {
236
            $result = $this->link->query('PRAGMA table_info('.$this->escape_table($table).')');
237
238
            foreach ($result as $row) {
239
                $tables[$table][$row['name']] = $this->sql_type($row['type']);
240
            }
241
242
            return $tables[$table];
243
        }
244
    }
245
246
    public function field_data($table)
247
    {
248
        Kohana::log('error', 'This method is under developing');
249
    }
250
    /**
251
     * Version number query string
252
     *
253
     * @access	public
254
     * @return	string
255
     */
256
    public function version()
257
    {
258
        return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
259
    }
260
} // End Database_PdoSqlite_Driver Class
261
262
/*
263
 * PDO-sqlite Result
264
 */
265
class Pdosqlite_Result extends Database_Result
266
{
267
268
    // Data fetching types
269
    protected $fetch_type  = PDO::FETCH_OBJ;
270
    protected $return_type = PDO::FETCH_ASSOC;
271
272
    /**
273
     * Sets up the result variables.
274
     *
275
     * @param  resource  query result
276
     * @param  resource  database link
277
     * @param  boolean   return objects or arrays
278
     * @param  string    SQL query that was run
279
     * @param PDOStatement $result
280
     * @param PDO $link
281
     */
282
    public function __construct($result, $link, $object = true, $sql)
283
    {
284
        if (is_object($result) or $result = $link->prepare($sql)) {
285
            // run the query. Return true if success, false otherwise
286
            if (! $result->execute()) {
287
                // Throw Kohana Exception with error message. See PDOStatement errorInfo() method
288
                $arr_infos = $result->errorInfo();
289
                throw new Kohana_Database_Exception('database.error', $arr_infos[2]);
290
            }
291
292
            if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql)) {
293
                $this->result = $result;
294
                $this->current_row = 0;
295
296
                $this->total_rows = $this->sqlite_row_count();
297
298
                $this->fetch_type = ($object === true) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC;
299
            } elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql)) {
300
                $this->insert_id  = $link->lastInsertId();
301
302
                $this->total_rows = $result->rowCount();
303
            }
304
        } else {
305
            // SQL error
306
            throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql);
307
        }
308
309
        // Set result type
310
        $this->result($object);
311
312
        // Store the SQL
313
        $this->sql = $sql;
314
    }
315
316
    private function sqlite_row_count()
317
    {
318
        $count = 0;
319
        while ($this->result->fetch()) {
0 ignored issues
show
Bug introduced by
The method fetch does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
320
            $count++;
321
        }
322
323
        // The query must be re-fetched now.
324
        $this->result->execute();
0 ignored issues
show
Bug introduced by
The method execute does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
325
326
        return $count;
327
    }
328
329
    /*
330
     * Destructor: __destruct
331
     *  Magic __destruct function, frees the result.
332
     */
333
    public function __destruct()
334
    {
335
        if (is_object($this->result)) {
336
            $this->result->closeCursor();
0 ignored issues
show
Bug introduced by
The method closeCursor does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
337
            $this->result = null;
338
        }
339
    }
340
341
    public function result($object = true, $type = PDO::FETCH_BOTH)
342
    {
343
        $this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH;
344
345
        if ($this->fetch_type == PDO::FETCH_OBJ) {
346
            $this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
347
        } else {
348
            $this->return_type = $type;
349
        }
350
351
        return $this;
352
    }
353
354
    public function as_array($object = null, $type = PDO::FETCH_ASSOC)
355
    {
356
        return $this->result_array($object, $type);
357
    }
358
359
    public function result_array($object = null, $type = PDO::FETCH_ASSOC)
360
    {
361
        $rows = array();
362
363
        if (is_string($object)) {
364
            $fetch = $object;
365
        } elseif (is_bool($object)) {
366
            if ($object === true) {
367
                $fetch = PDO::FETCH_OBJ;
368
369
                // NOTE - The class set by $type must be defined before fetching the result,
370
                // autoloading is disabled to save a lot of stupid overhead.
371
                $type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
0 ignored issues
show
Unused Code introduced by
$type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
372
            } else {
373
                $fetch = PDO::FETCH_OBJ;
374
            }
375
        } else {
376
            // Use the default config values
377
            $fetch = $this->fetch_type;
378
379
            if ($fetch == PDO::FETCH_OBJ) {
380
                $type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
0 ignored issues
show
Unused Code introduced by
$type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
381
            }
382
        }
383
        try {
384
            while ($row = $this->result->fetch($fetch)) {
0 ignored issues
show
Bug introduced by
The method fetch does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
385
                $rows[] = $row;
386
            }
387
        } catch (PDOException $e) {
388
            throw new Kohana_Database_Exception('database.error', $e->getMessage());
389
            return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
390
        }
391
        return $rows;
392
    }
393
394
    public function list_fields()
395
    {
396
        $field_names = array();
397
        for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++) {
0 ignored issues
show
Bug introduced by
The method columnCount does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
398
            $info = $this->result->getColumnMeta($i);
0 ignored issues
show
Bug introduced by
The method getColumnMeta does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
399
            $field_names[] = $info['name'];
400
        }
401
        return $field_names;
402
    }
403
404
    /**
405
     * @param integer $offset
406
     */
407
    public function seek($offset)
408
    {
409
        // To request a scrollable cursor for your PDOStatement object, you must
410
        // set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you
411
        // prepare the statement.
412
        Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored');
413
414
        return false;
415
    }
416
417
    public function offsetGet($offset)
418
    {
419
        try {
420
            return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset);
0 ignored issues
show
Bug introduced by
The method fetch does only exist in PDOStatement, but not in mysqli_result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
421
        } catch (PDOException $e) {
422
            throw new Kohana_Database_Exception('database.error', $e->getMessage());
423
        }
424
    }
425
426
    public function rewind()
427
    {
428
        // Same problem that seek() has, see above.
429
        return $this->seek(0);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->seek(0); (boolean) is incompatible with the return type of the parent method Database_Result::rewind of type Database_Result.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
430
    }
431
} // End PdoSqlite_Result Class
432