Passed
Pull Request — master (#29)
by Alexandru
01:21
created

PostgresInsertCompiler._assert_valid_field()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
c 3
b 0
f 0
dl 0
loc 10
rs 9.4285
ccs 5
cts 5
cp 1
crap 2
1 1
from django.core.exceptions import SuspiciousOperation
0 ignored issues
show
Configuration introduced by
The import django.core.exceptions could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
2 1
from django.db.models.sql.compiler import SQLInsertCompiler, SQLUpdateCompiler
0 ignored issues
show
Configuration introduced by
The import django.db.models.sql.compiler could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
3
4
5 1
class PostgresReturningUpdateCompiler(SQLUpdateCompiler):
0 ignored issues
show
Coding Style introduced by
This class has no __init__ method.
Loading history...
6
    """Compiler for SQL UPDATE statements that return
7
    the primary keys of the affected rows."""
8
9 1
    def execute_sql(self, _result_type):
0 ignored issues
show
Unused Code introduced by
The argument _result_type seems to be unused.
Loading history...
10 1
        sql, params = self.as_sql()
0 ignored issues
show
Bug introduced by
The Instance of PostgresReturningUpdateCompiler does not seem to have a member named as_sql.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
11 1
        sql += self._form_returning()
12
13 1
        with self.connection.cursor() as cursor:
0 ignored issues
show
Bug introduced by
The Instance of PostgresReturningUpdateCompiler does not seem to have a member named connection.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
14 1
            cursor.execute(sql, params)
15 1
            primary_keys = cursor.fetchall()
16
17 1
        return primary_keys
18
19 1
    def _form_returning(self):
20
        """Builds the RETURNING part of the query."""
21
22 1
        qn = self.connection.ops.quote_name
0 ignored issues
show
Bug introduced by
The Instance of PostgresReturningUpdateCompiler does not seem to have a member named connection.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
23 1
        return ' RETURNING %s' % qn(self.query.model._meta.pk.name)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _meta was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Bug introduced by
The Instance of PostgresReturningUpdateCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
24
25
26 1
class PostgresInsertCompiler(SQLInsertCompiler):
27
    """Compiler for SQL INSERT statements."""
28
29 1
    def __init__(self, *args, **kwargs):
30
        """Initializes a new instance of :see:PostgresInsertCompiler."""
31
32 1
        super().__init__(*args, **kwargs)
33 1
        self.qn = self.connection.ops.quote_name
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named connection.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
34
35 1
    def as_sql(self, return_id=False):
36
        """Builds the SQL INSERT statement."""
37
38 1
        queries = [
39
            self._rewrite_insert(sql, params, return_id)
40
            for sql, params in super().as_sql()
41
        ]
42
43 1
        return queries
44
45 1
    def execute_sql(self, return_id=False):
46
        # execute all the generate queries
47 1
        with self.connection.cursor() as cursor:
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named connection.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
48 1
            rows = []
49 1
            for sql, params in self.as_sql(return_id):
50 1
                cursor.execute(sql, params)
51 1
                rows.append(cursor.fetchone())
52
53
        # create a mapping between column names and column value
54 1
        return [
55
            {
56
                column.name: row[column_index]
57
                for column_index, column in enumerate(cursor.description) if row
58
            }
59
            for row in rows
60
        ]
61
62 1
    def _rewrite_insert(self, sql, params, return_id=False):
63
        """Rewrites a formed SQL INSERT query to include
64
        the ON CONFLICT clause.
65
66
        Arguments:
67
            sql:
68
                The SQL INSERT query to rewrite.
69
70
            params:
71
                The parameters passed to the query.
72
73
            returning:
74
                What to put in the `RETURNING` clause
75
                of the resulting query.
76
77
        Returns:
78
            A tuple of the rewritten SQL query and new params.
79
        """
80
81 1
        returning = self.qn(self.query.model._meta.pk.name) if return_id else '*'
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _meta was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
82
83 1
        if self.query.conflict_action.value == 'UPDATE':
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
84 1
            return self._rewrite_insert_update(sql, params, returning)
85 1
        elif self.query.conflict_action.value == 'NOTHING':
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
86 1
            return self._rewrite_insert_nothing(sql, params, returning)
87
88
        raise SuspiciousOperation((
89
            '%s is not a valid conflict action, specify '
90
            'ConflictAction.UPDATE or ConflictAction.NOTHING.'
91
        ) % str(self.query.conflict_action))
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
92
93 1
    def _rewrite_insert_update(self, sql, params, returning):
94
        """Rewrites a formed SQL INSERT query to include
95
        the ON CONFLICT DO UPDATE clause."""
96
97 1
        update_columns = ', '.join([
98
            '{0} = EXCLUDED.{0}'.format(self.qn(field.column))
99
            for field in self.query.update_fields
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
100
        ])
101
102
        # build the conflict target, the columns to watch
103
        # for conflicts
104 1
        conflict_target = self._build_conflict_target()
105
106 1
        index_predicate = self.query.index_predicate
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
107
108 1
        sql_template = \
109
            (
110
                '{insert} ON CONFLICT {conflict_target} WHERE {index_predicate} DO UPDATE'
111
                ' SET {update_columns} RETURNING {returning}'
112
            ) if index_predicate else (
113
                '{insert} ON CONFLICT {conflict_target} DO UPDATE'
114
                ' SET {update_columns} RETURNING {returning}'
115
            )
116 1
        return (
117
            sql_template.format(
118
                insert=sql,
119
                conflict_target=conflict_target,
120
                update_columns=update_columns,
121
                returning=returning,
122
                index_predicate=index_predicate,
123
            ),
124
            params
125
        )
126
127 1
    def _rewrite_insert_nothing(self, sql, params, returning):
128
        """Rewrites a formed SQL INSERT query to include
129
        the ON CONFLICT DO NOTHING clause."""
130
131
        # build the conflict target, the columns to watch
132
        # for conflicts
133 1
        conflict_target = self._build_conflict_target()
134
135 1
        where_clause = ' AND '.join([
136
            '{0} = %s'.format(self._format_field_name(field_name))
137
            for field_name in self.query.conflict_target
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
138
        ])
139
140 1
        where_clause_params = [
141
            self._format_field_value(field_name)
142
            for field_name in self.query.conflict_target
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
143
        ]
144
145 1
        params = params + tuple(where_clause_params)
146
147
        # this looks complicated, and it is, but it is for a reason... a normal
148
        # ON CONFLICT DO NOTHING doesn't return anything if the row already exists
149
        # so we do DO UPDATE instead that never executes to lock the row, and then
150
        # select from the table in case we're dealing with an existing row..
151 1
        return (
152
            (
153
                'WITH insdata AS ('
154
                '{insert} ON CONFLICT {conflict_target} DO UPDATE'
155
                ' SET id = NULL WHERE FALSE RETURNING {returning})'
156
                ' SELECT * FROM insdata UNION ALL'
157
                ' SELECT {returning} FROM {table} WHERE {where_clause} LIMIT 1;'
158
            ).format(
159
                insert=sql,
160
                conflict_target=conflict_target,
161
                returning=returning,
162
                table=self.query.objs[0]._meta.db_table,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _meta was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
163
                where_clause=where_clause
164
            ),
165
            params
166
        )
167
168 1
    def _build_conflict_target(self):
169
        """Builds the `conflict_target` for the ON CONFLICT
170
        clause."""
171
172 1
        conflict_target = []
173
174 1
        if not isinstance(self.query.conflict_target, list):
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
175
            raise SuspiciousOperation((
176
                '%s is not a valid conflict target, specify '
177
                'a list of column names, or tuples with column '
178
                'names and hstore key.'
179
            ) % str(self.query.conflict_target))
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
180
181 1
        def _assert_valid_field(field_name):
182 1
            field_name = self._normalize_field_name(field_name)
183 1
            if self._get_model_field(field_name):
184 1
                return
185
186 1
            raise SuspiciousOperation((
187
                '%s is not a valid conflict target, specify '
188
                'a list of column names, or tuples with column '
189
                'names and hstore key.'
190
            ) % str(field_name))
191
192 1
        for field_name in self.query.conflict_target:
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
193 1
            _assert_valid_field(field_name)
194
195
            # special handling for hstore keys
196 1
            if isinstance(field_name, tuple):
197 1
                conflict_target.append(
198
                    '(%s->\'%s\')' % (
199
                        self._format_field_name(field_name),
200
                        field_name[1]
201
                    )
202
                )
203
            else:
204 1
                conflict_target.append(
205
                    self._format_field_name(field_name))
206
207 1
        return '(%s)' % ','.join(conflict_target)
208
209 1
    def _get_model_field(self, name: str):
210
        """Gets the field on a model with the specified name.
211
212
        Arguments:
213
            name:
214
                The name of the field to look for.
215
216
                This can be both the actual field name, or
217
                the name of the column, both will work :)
218
219
        Returns:
220
            The field with the specified name or None if
221
            no such field exists.
222
        """
223
224 1
        field_name = self._normalize_field_name(name)
225
226
        # 'pk' has special meaning and always refers to the primary
227
        # key of a model, we have to respect this de-facto standard behaviour
228 1
        if field_name == 'pk' and self.query.model._meta.pk:
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _meta was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
229 1
            return self.query.model._meta.pk
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _meta was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
230
231 1
        for field in self.query.model._meta.local_concrete_fields:
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _meta was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
232 1
            if field.name == field_name or field.column == field_name:
233 1
                return field
234
235 1
        return None
236
237 1
    def _format_field_name(self, field_name) -> str:
238
        """Formats a field's name for usage in SQL.
239
240
        Arguments:
241
            field_name:
242
                The field name to format.
243
244
        Returns:
245
            The specified field name formatted for
246
            usage in SQL.
247
        """
248
249 1
        field = self._get_model_field(field_name)
250 1
        return self.qn(field.column)
251
252 1
    def _format_field_value(self, field_name) -> str:
253
        """Formats a field's value for usage in SQL.
254
255
        Arguments:
256
            field_name:
257
                The name of the field to format
258
                the value of.
259
260
        Returns:
261
            The field's value formatted for usage
262
            in SQL.
263
        """
264
265 1
        field_name = self._normalize_field_name(field_name)
266 1
        field = self._get_model_field(field_name)
267
268 1
        return SQLInsertCompiler.prepare_value(
269
            self,
270
            field,
271
            getattr(self.query.objs[0], field_name)
0 ignored issues
show
Bug introduced by
The Instance of PostgresInsertCompiler does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
272
        )
273
274 1
    def _normalize_field_name(self, field_name) -> str:
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
275
        """Normalizes a field name into a string by
276
        extracting the field name if it was specified
277
        as a reference to a HStore key (as a tuple).
278
279
        Arguments:
280
            field_name:
281
                The field name to normalize.
282
283
        Returns:
284
            The normalized field name.
285
        """
286
287 1
        if isinstance(field_name, tuple):
288 1
            field_name, _ = field_name
289
290
        return field_name
291