sqlalchemy-cratedb 0.41.0.dev0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,423 @@
1
+ # -*- coding: utf-8; -*-
2
+ #
3
+ # Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
4
+ # license agreements. See the NOTICE file distributed with this work for
5
+ # additional information regarding copyright ownership. Crate licenses
6
+ # this file to you under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License. You may
8
+ # obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations
16
+ # under the License.
17
+ #
18
+ # However, if you have executed another commercial license agreement
19
+ # with Crate these terms will supersede the license and you may use the
20
+ # software solely pursuant to the terms of the relevant commercial agreement.
21
+
22
+ # ruff: noqa: S101 # Use of `assert` detected
23
+
24
+ from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union
25
+
26
+ import sqlalchemy as sa
27
+ from sqlalchemy import ColumnClause, ValuesBase, cast, exc
28
+ from sqlalchemy.sql import dml
29
+ from sqlalchemy.sql.base import _from_objects
30
+ from sqlalchemy.sql.compiler import SQLCompiler
31
+ from sqlalchemy.sql.crud import (
32
+ REQUIRED,
33
+ _as_dml_column,
34
+ _create_bind_param,
35
+ _CrudParamElement,
36
+ _CrudParams,
37
+ _extend_values_for_multiparams,
38
+ _get_stmt_parameter_tuples_params,
39
+ _get_update_multitable_params,
40
+ _key_getters_for_crud_column,
41
+ _scan_cols,
42
+ _scan_insert_from_select_cols,
43
+ _setup_delete_return_defaults,
44
+ )
45
+ from sqlalchemy.sql.dml import DMLState, _DMLColumnElement
46
+ from sqlalchemy.sql.dml import isinsert as _compile_state_isinsert
47
+
48
+ from sqlalchemy_cratedb.compiler import CrateCompiler
49
+
50
+
51
+ class CrateCompilerSA20(CrateCompiler):
52
+ def visit_update(self, update_stmt, **kw):
53
+ compile_state = update_stmt._compile_state_factory(update_stmt, self, **kw)
54
+ update_stmt = compile_state.statement
55
+
56
+ # [20] CrateDB patch.
57
+ if not compile_state._dict_parameters and not hasattr(update_stmt, "_crate_specific"):
58
+ return super().visit_update(update_stmt, **kw)
59
+
60
+ toplevel = not self.stack
61
+ if toplevel:
62
+ self.isupdate = True
63
+ if not self.dml_compile_state:
64
+ self.dml_compile_state = compile_state
65
+ if not self.compile_state:
66
+ self.compile_state = compile_state
67
+
68
+ extra_froms = compile_state._extra_froms
69
+ is_multitable = bool(extra_froms)
70
+
71
+ if is_multitable:
72
+ # main table might be a JOIN
73
+ main_froms = set(_from_objects(update_stmt.table))
74
+ render_extra_froms = [f for f in extra_froms if f not in main_froms]
75
+ correlate_froms = main_froms.union(extra_froms)
76
+ else:
77
+ render_extra_froms = []
78
+ correlate_froms = {update_stmt.table}
79
+
80
+ self.stack.append(
81
+ {
82
+ "correlate_froms": correlate_froms,
83
+ "asfrom_froms": correlate_froms,
84
+ "selectable": update_stmt,
85
+ }
86
+ )
87
+
88
+ text = "UPDATE "
89
+
90
+ if update_stmt._prefixes:
91
+ text += self._generate_prefixes(update_stmt, update_stmt._prefixes, **kw)
92
+
93
+ table_text = self.update_tables_clause(
94
+ update_stmt, update_stmt.table, render_extra_froms, **kw
95
+ )
96
+ # [20] CrateDB patch.
97
+ crud_params_struct = _get_crud_params(self, update_stmt, compile_state, toplevel, **kw)
98
+ crud_params = crud_params_struct.single_params
99
+
100
+ if update_stmt._hints:
101
+ dialect_hints, table_text = self._setup_crud_hints(update_stmt, table_text)
102
+ else:
103
+ dialect_hints = None
104
+
105
+ if update_stmt._independent_ctes:
106
+ self._dispatch_independent_ctes(update_stmt, kw)
107
+
108
+ text += table_text
109
+
110
+ text += " SET "
111
+
112
+ # [20] CrateDB patch begin.
113
+ include_table = extra_froms and self.render_table_with_column_in_update_from
114
+
115
+ set_clauses = []
116
+
117
+ for c, expr, value, _ in crud_params: # noqa: B007
118
+ key = c._compiler_dispatch(self, include_table=include_table)
119
+ clause = key + " = " + value
120
+ set_clauses.append(clause)
121
+
122
+ for k, v in compile_state._dict_parameters.items():
123
+ if isinstance(k, str) and "[" in k:
124
+ bindparam = sa.sql.bindparam(k, v)
125
+ clause = k + " = " + self.process(bindparam)
126
+ set_clauses.append(clause)
127
+
128
+ text += ", ".join(set_clauses)
129
+ # [20] CrateDB patch end.
130
+
131
+ if self.implicit_returning or update_stmt._returning:
132
+ if self.returning_precedes_values:
133
+ text += " " + self.returning_clause(
134
+ update_stmt,
135
+ self.implicit_returning or update_stmt._returning,
136
+ populate_result_map=toplevel,
137
+ )
138
+
139
+ if extra_froms:
140
+ extra_from_text = self.update_from_clause(
141
+ update_stmt,
142
+ update_stmt.table,
143
+ render_extra_froms,
144
+ dialect_hints,
145
+ **kw,
146
+ )
147
+ if extra_from_text:
148
+ text += " " + extra_from_text
149
+
150
+ if update_stmt._where_criteria:
151
+ t = self._generate_delimited_and_list(update_stmt._where_criteria, **kw)
152
+ if t:
153
+ text += " WHERE " + t
154
+
155
+ limit_clause = self.update_limit_clause(update_stmt)
156
+ if limit_clause:
157
+ text += " " + limit_clause
158
+
159
+ if (
160
+ self.implicit_returning or update_stmt._returning
161
+ ) and not self.returning_precedes_values:
162
+ text += " " + self.returning_clause(
163
+ update_stmt,
164
+ self.implicit_returning or update_stmt._returning,
165
+ populate_result_map=toplevel,
166
+ )
167
+
168
+ if self.ctes:
169
+ nesting_level = len(self.stack) if not toplevel else None
170
+ text = self._render_cte_clause(nesting_level=nesting_level) + text
171
+
172
+ self.stack.pop(-1)
173
+
174
+ return text
175
+
176
+
177
+ def _get_crud_params(
178
+ compiler: SQLCompiler,
179
+ stmt: ValuesBase,
180
+ compile_state: DMLState,
181
+ toplevel: bool,
182
+ **kw: Any,
183
+ ) -> _CrudParams:
184
+ """create a set of tuples representing column/string pairs for use
185
+ in an INSERT or UPDATE statement.
186
+
187
+ Also generates the Compiled object's postfetch, prefetch, and
188
+ returning column collections, used for default handling and ultimately
189
+ populating the CursorResult's prefetch_cols() and postfetch_cols()
190
+ collections.
191
+
192
+ """
193
+
194
+ # note: the _get_crud_params() system was written with the notion in mind
195
+ # that INSERT, UPDATE, DELETE are always the top level statement and
196
+ # that there is only one of them. With the addition of CTEs that can
197
+ # make use of DML, this assumption is no longer accurate; the DML
198
+ # statement is not necessarily the top-level "row returning" thing
199
+ # and it is also theoretically possible (fortunately nobody has asked yet)
200
+ # to have a single statement with multiple DMLs inside of it via CTEs.
201
+
202
+ # the current _get_crud_params() design doesn't accommodate these cases
203
+ # right now. It "just works" for a CTE that has a single DML inside of
204
+ # it, and for a CTE with multiple DML, it's not clear what would happen.
205
+
206
+ # overall, the "compiler.XYZ" collections here would need to be in a
207
+ # per-DML structure of some kind, and DefaultDialect would need to
208
+ # navigate these collections on a per-statement basis, with additional
209
+ # emphasis on the "toplevel returning data" statement. However we
210
+ # still need to run through _get_crud_params() for all DML as we have
211
+ # Python / SQL generated column defaults that need to be rendered.
212
+
213
+ # if there is user need for this kind of thing, it's likely a post 2.0
214
+ # kind of change as it would require deep changes to DefaultDialect
215
+ # as well as here.
216
+
217
+ compiler.postfetch = []
218
+ compiler.insert_prefetch = []
219
+ compiler.update_prefetch = []
220
+ compiler.implicit_returning = []
221
+
222
+ # getters - these are normally just column.key,
223
+ # but in the case of mysql multi-table update, the rules for
224
+ # .key must conditionally take tablename into account
225
+ (
226
+ _column_as_key,
227
+ _getattr_col_key,
228
+ _col_bind_name,
229
+ ) = _key_getters_for_crud_column(compiler, stmt, compile_state)
230
+
231
+ compiler._get_bind_name_for_col = _col_bind_name
232
+
233
+ if stmt._returning and stmt._return_defaults:
234
+ raise exc.CompileError(
235
+ "Can't compile statement that includes returning() and return_defaults() simultaneously"
236
+ )
237
+
238
+ if compile_state.isdelete:
239
+ _setup_delete_return_defaults(
240
+ compiler,
241
+ stmt,
242
+ compile_state,
243
+ (),
244
+ _getattr_col_key,
245
+ _column_as_key,
246
+ _col_bind_name,
247
+ (),
248
+ (),
249
+ toplevel,
250
+ kw,
251
+ )
252
+ return _CrudParams([], [])
253
+
254
+ # no parameters in the statement, no parameters in the
255
+ # compiled params - return binds for all columns
256
+ if compiler.column_keys is None and compile_state._no_parameters:
257
+ return _CrudParams(
258
+ [
259
+ (
260
+ c,
261
+ compiler.preparer.format_column(c),
262
+ _create_bind_param(compiler, c, None, required=True),
263
+ (c.key,),
264
+ )
265
+ for c in stmt.table.columns
266
+ ],
267
+ [],
268
+ )
269
+
270
+ stmt_parameter_tuples: Optional[List[Tuple[Union[str, ColumnClause[Any]], Any]]]
271
+ spd: Optional[MutableMapping[_DMLColumnElement, Any]]
272
+
273
+ if _compile_state_isinsert(compile_state) and compile_state._has_multi_parameters:
274
+ mp = compile_state._multi_parameters
275
+ assert mp is not None
276
+ spd = mp[0]
277
+ stmt_parameter_tuples = list(spd.items())
278
+ elif compile_state._ordered_values:
279
+ spd = compile_state._dict_parameters
280
+ stmt_parameter_tuples = compile_state._ordered_values
281
+ elif compile_state._dict_parameters:
282
+ spd = compile_state._dict_parameters
283
+ stmt_parameter_tuples = list(spd.items())
284
+ else:
285
+ stmt_parameter_tuples = spd = None
286
+
287
+ # if we have statement parameters - set defaults in the
288
+ # compiled params
289
+ if compiler.column_keys is None:
290
+ parameters = {}
291
+ elif stmt_parameter_tuples:
292
+ assert spd is not None
293
+ parameters = {
294
+ _column_as_key(key): REQUIRED for key in compiler.column_keys if key not in spd
295
+ }
296
+ else:
297
+ parameters = {_column_as_key(key): REQUIRED for key in compiler.column_keys}
298
+
299
+ # create a list of column assignment clauses as tuples
300
+ values: List[_CrudParamElement] = []
301
+
302
+ if stmt_parameter_tuples is not None:
303
+ _get_stmt_parameter_tuples_params(
304
+ compiler,
305
+ compile_state,
306
+ parameters,
307
+ stmt_parameter_tuples,
308
+ _column_as_key,
309
+ values,
310
+ kw,
311
+ )
312
+
313
+ check_columns: Dict[str, ColumnClause[Any]] = {}
314
+
315
+ # special logic that only occurs for multi-table UPDATE
316
+ # statements
317
+ if dml.isupdate(compile_state) and compile_state.is_multitable:
318
+ _get_update_multitable_params(
319
+ compiler,
320
+ stmt,
321
+ compile_state,
322
+ stmt_parameter_tuples,
323
+ check_columns,
324
+ _col_bind_name,
325
+ _getattr_col_key,
326
+ values,
327
+ kw,
328
+ )
329
+
330
+ if _compile_state_isinsert(compile_state) and stmt._select_names:
331
+ # is an insert from select, is not a multiparams
332
+
333
+ assert not compile_state._has_multi_parameters
334
+
335
+ _scan_insert_from_select_cols(
336
+ compiler,
337
+ stmt,
338
+ compile_state,
339
+ parameters,
340
+ _getattr_col_key,
341
+ _column_as_key,
342
+ _col_bind_name,
343
+ check_columns,
344
+ values,
345
+ toplevel,
346
+ kw,
347
+ )
348
+ else:
349
+ _scan_cols(
350
+ compiler,
351
+ stmt,
352
+ compile_state,
353
+ parameters,
354
+ _getattr_col_key,
355
+ _column_as_key,
356
+ _col_bind_name,
357
+ check_columns,
358
+ values,
359
+ toplevel,
360
+ kw,
361
+ )
362
+
363
+ # [20] CrateDB patch.
364
+ #
365
+ # This sanity check performed by SQLAlchemy currently needs to be
366
+ # deactivated in order to satisfy the rewriting logic of the CrateDB
367
+ # dialect in `rewrite_update` and `visit_update`.
368
+ #
369
+ # It can be quickly reproduced by activating this section and running the
370
+ # test cases::
371
+ #
372
+ # ./bin/test -vvvv -t dict_test
373
+ #
374
+ # That croaks like::
375
+ #
376
+ # sqlalchemy.exc.CompileError: Unconsumed column names: characters_name
377
+ #
378
+ # TODO: Investigate why this is actually happening and eventually mitigate
379
+ # the root cause.
380
+ """
381
+ if parameters and stmt_parameter_tuples:
382
+ check = (
383
+ set(parameters)
384
+ .intersection(_column_as_key(k) for k, v in stmt_parameter_tuples)
385
+ .difference(check_columns)
386
+ )
387
+ if check:
388
+ raise exc.CompileError(
389
+ "Unconsumed column names: %s"
390
+ % (", ".join("%s" % (c,) for c in check))
391
+ )
392
+ """
393
+
394
+ if _compile_state_isinsert(compile_state) and compile_state._has_multi_parameters:
395
+ # is a multiparams, is not an insert from a select
396
+ assert not stmt._select_names
397
+ multi_extended_values = _extend_values_for_multiparams(
398
+ compiler,
399
+ stmt,
400
+ compile_state,
401
+ cast(
402
+ "Sequence[_CrudParamElementStr]",
403
+ values,
404
+ ),
405
+ cast("Callable[..., str]", _column_as_key),
406
+ kw,
407
+ )
408
+ return _CrudParams(values, multi_extended_values)
409
+ elif not values and compiler.for_executemany and compiler.dialect.supports_default_metavalue:
410
+ # convert an "INSERT DEFAULT VALUES"
411
+ # into INSERT (firstcol) VALUES (DEFAULT) which can be turned
412
+ # into an in-place multi values. This supports
413
+ # insert_executemany_returning mode :)
414
+ values = [
415
+ (
416
+ _as_dml_column(stmt.table.columns[0]),
417
+ compiler.preparer.format_column(stmt.table.columns[0]),
418
+ compiler.dialect.default_metavalue_token,
419
+ (),
420
+ )
421
+ ]
422
+
423
+ return _CrudParams(values, [])