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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, [])