Completed
Push — master ( 84dee3...084275 )
by Michal
36:03
created

SqlDialectTrait::_transformDistinct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @since         3.0.0
13
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Database;
16
17
use Cake\Database\Expression\Comparison;
18
19
/**
20
 * Sql dialect trait
21
 */
22
trait SqlDialectTrait
23
{
24
25
    /**
26
     * Quotes a database identifier (a column name, table name, etc..) to
27
     * be used safely in queries without the risk of using reserved words
28
     *
29
     * @param string $identifier The identifier to quote.
30
     * @return string
31
     */
32
    public function quoteIdentifier($identifier)
33
    {
34
        $identifier = trim($identifier);
35
36
        if ($identifier === '*') {
37
            return '*';
38
        }
39
40
        if ($identifier === '') {
41
            return '';
42
        }
43
44
        // string
45
        if (preg_match('/^[\w-]+$/', $identifier)) {
46
            return $this->_startQuote . $identifier . $this->_endQuote;
0 ignored issues
show
Bug introduced by
The property _startQuote 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...
Bug introduced by
The property _endQuote 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...
47
        }
48
49
        if (preg_match('/^[\w-]+\.[^ \*]*$/', $identifier)) {
50
// string.string
51
            $items = explode('.', $identifier);
52
            return $this->_startQuote . implode($this->_endQuote . '.' . $this->_startQuote, $items) . $this->_endQuote;
53
        }
54
55
        if (preg_match('/^[\w-]+\.\*$/', $identifier)) {
56
// string.*
57
            return $this->_startQuote . str_replace('.*', $this->_endQuote . '.*', $identifier);
58
        }
59
60 View Code Duplication
        if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) {
61
// Functions
62
            return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
63
        }
64
65
        // Alias.field AS thing
66 View Code Duplication
        if (preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+AS\s*([\w-]+)$/i', $identifier, $matches)) {
67
            return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]);
68
        }
69
70
        if (preg_match('/^[\w-_\s]*[\w-_]+/', $identifier)) {
71
            return $this->_startQuote . $identifier . $this->_endQuote;
72
        }
73
74
        return $identifier;
75
    }
76
77
    /**
78
     * Returns a callable function that will be used to transform a passed Query object.
79
     * This function, in turn, will return an instance of a Query object that has been
80
     * transformed to accommodate any specificities of the SQL dialect in use.
81
     *
82
     * @param string $type the type of query to be transformed
83
     * (select, insert, update, delete)
84
     * @return callable
85
     */
86
    public function queryTranslator($type)
87
    {
88
        return function ($query) use ($type) {
89
            if ($this->autoQuoting()) {
0 ignored issues
show
Bug introduced by
It seems like autoQuoting() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
90
                $query = (new IdentifierQuoter($this))->quote($query);
91
            }
92
93
            $query = $this->{'_' . $type . 'QueryTranslator'}($query);
94
            $translators = $this->_expressionTranslators();
95
            if (!$translators) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $translators of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
96
                return $query;
97
            }
98
99
            $query->traverseExpressions(function ($expression) use ($translators, $query) {
100
                foreach ($translators as $class => $method) {
101
                    if ($expression instanceof $class) {
102
                        $this->{$method}($expression, $query);
103
                    }
104
                }
105
            });
106
            return $query;
107
        };
108
    }
109
110
    /**
111
     * Returns an associative array of methods that will transform Expression
112
     * objects to conform with the specific SQL dialect. Keys are class names
113
     * and values a method in this class.
114
     *
115
     * @return array
116
     */
117
    protected function _expressionTranslators()
118
    {
119
        return [];
120
    }
121
122
    /**
123
     * Apply translation steps to select queries.
124
     *
125
     * @param \Cake\Database\Query $query The query to translate
126
     * @return \Cake\Database\Query The modified query
127
     */
128
    protected function _selectQueryTranslator($query)
129
    {
130
        return $this->_transformDistinct($query);
131
    }
132
133
    /**
134
     * Returns the passed query after rewriting the DISTINCT clause, so that drivers
135
     * that do not support the "ON" part can provide the actual way it should be done
136
     *
137
     * @param \Cake\Database\Query $query The query to be transformed
138
     * @return \Cake\Database\Query
139
     */
140
    protected function _transformDistinct($query)
141
    {
142
        if (is_array($query->clause('distinct'))) {
143
            $query->group($query->clause('distinct'), true);
144
            $query->distinct(false);
145
        }
146
        return $query;
147
    }
148
149
    /**
150
     * Apply translation steps to delete queries.
151
     *
152
     * Chops out aliases on delete query conditions as most database dialects do not
153
     * support aliases in delete queries. This also removes aliases
154
     * in table names as they frequently don't work either.
155
     *
156
     * We are intentionally not supporting deletes with joins as they have even poorer support.
157
     *
158
     * @param \Cake\Database\Query $query The query to translate
159
     * @return \Cake\Database\Query The modified query
160
     */
161
    protected function _deleteQueryTranslator($query)
162
    {
163
        $hadAlias = false;
164
        $tables = [];
165
        foreach ($query->clause('from') as $alias => $table) {
166
            if (is_string($alias)) {
167
                $hadAlias = true;
168
            }
169
            $tables[] = $table;
170
        }
171
        if ($hadAlias) {
172
            $query->from($tables, true);
173
        }
174
175
        if (!$hadAlias) {
176
            return $query;
177
        }
178
        $conditions = $query->clause('where');
179
        if ($conditions) {
180
            $conditions->traverse(function ($condition) {
181
                if (!($condition instanceof Comparison)) {
182
                    return $condition;
183
                }
184
185
                $field = $condition->getField();
186
                if ($field instanceof ExpressionInterface || strpos($field, '.') === false) {
187
                    return $condition;
188
                }
189
190
                list(, $field) = explode('.', $field);
191
                $condition->setField($field);
192
                return $condition;
193
            });
194
        }
195
196
        return $query;
197
    }
198
199
    /**
200
     * Apply translation steps to update queries.
201
     *
202
     * @param \Cake\Database\Query $query The query to translate
203
     * @return \Cake\Database\Query The modified query
204
     */
205
    protected function _updateQueryTranslator($query)
206
    {
207
        return $query;
208
    }
209
210
    /**
211
     * Apply translation steps to insert queries.
212
     *
213
     * @param \Cake\Database\Query $query The query to translate
214
     * @return \Cake\Database\Query The modified query
215
     */
216
    protected function _insertQueryTranslator($query)
217
    {
218
        return $query;
219
    }
220
221
    /**
222
     * Returns a SQL snippet for creating a new transaction savepoint
223
     *
224
     * @param string $name save point name
225
     * @return string
226
     */
227
    public function savePointSQL($name)
228
    {
229
        return 'SAVEPOINT LEVEL' . $name;
230
    }
231
232
    /**
233
     * Returns a SQL snippet for releasing a previously created save point
234
     *
235
     * @param string $name save point name
236
     * @return string
237
     */
238
    public function releaseSavePointSQL($name)
239
    {
240
        return 'RELEASE SAVEPOINT LEVEL' . $name;
241
    }
242
243
    /**
244
     * Returns a SQL snippet for rollbacking a previously created save point
245
     *
246
     * @param string $name save point name
247
     * @return string
248
     */
249
    public function rollbackSavePointSQL($name)
250
    {
251
        return 'ROLLBACK TO SAVEPOINT LEVEL' . $name;
252
    }
253
}
254