django-gbase8s 2.2.0__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.
- django_gbase8s/__init__.py +1 -0
- django_gbase8s/base.py +288 -0
- django_gbase8s/client.py +18 -0
- django_gbase8s/compiler.py +275 -0
- django_gbase8s/creation.py +15 -0
- django_gbase8s/features.py +124 -0
- django_gbase8s/introspection.py +338 -0
- django_gbase8s/operations.py +440 -0
- django_gbase8s/schema.py +507 -0
- django_gbase8s/utils.py +93 -0
- django_gbase8s/validation.py +23 -0
- django_gbase8s-2.2.0.dist-info/LICENSE +21 -0
- django_gbase8s-2.2.0.dist-info/METADATA +67 -0
- django_gbase8s-2.2.0.dist-info/RECORD +16 -0
- django_gbase8s-2.2.0.dist-info/WHEEL +5 -0
- django_gbase8s-2.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0"
|
django_gbase8s/base.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import decimal
|
|
2
|
+
import copy
|
|
3
|
+
import re
|
|
4
|
+
from collections import Mapping
|
|
5
|
+
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
8
|
+
from django.db.backends.base.base import BaseDatabaseWrapper
|
|
9
|
+
from django.db.backends.base.base import NO_DB_ALIAS
|
|
10
|
+
from django.utils.functional import cached_property
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import gbase8sdb as Database
|
|
14
|
+
Database.defaults.fetch_decimals = True
|
|
15
|
+
Database.defaults.fetch_lobs = False
|
|
16
|
+
except ImportError as e:
|
|
17
|
+
raise ImproperlyConfigured("Error loading gbase8sdb module: %s" % e)
|
|
18
|
+
|
|
19
|
+
from .client import DatabaseClient
|
|
20
|
+
from .creation import DatabaseCreation
|
|
21
|
+
from .features import DatabaseFeatures
|
|
22
|
+
from .introspection import DatabaseIntrospection
|
|
23
|
+
from .operations import DatabaseOperations
|
|
24
|
+
from .schema import DatabaseSchemaEditor
|
|
25
|
+
from .validation import DatabaseValidation
|
|
26
|
+
from .utils import dsn
|
|
27
|
+
from .utils import GBase8sParam
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
FORMAT_QMARK_REGEX = re.compile(r"(?<!%)%s")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CursorWrapper(object):
|
|
35
|
+
def __init__(self, cursor):
|
|
36
|
+
self.cursor = cursor
|
|
37
|
+
|
|
38
|
+
def _format_params(self, params):
|
|
39
|
+
if isinstance(params, (list, tuple)):
|
|
40
|
+
return tuple(GBase8sParam(p) for p in params)
|
|
41
|
+
elif isinstance(params, dict):
|
|
42
|
+
return {k: GBase8sParam(v) for k, v in params.items()}
|
|
43
|
+
else:
|
|
44
|
+
raise TypeError("params must be tuple or dict")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _param_generator(self, gbase8sparams):
|
|
48
|
+
if isinstance(gbase8sparams, (list, tuple)):
|
|
49
|
+
return tuple(p.param for p in gbase8sparams)
|
|
50
|
+
elif isinstance(gbase8sparams, dict):
|
|
51
|
+
return {k: v.param for k, v in gbase8sparams.items()}
|
|
52
|
+
else:
|
|
53
|
+
raise TypeError("params must be tuple or dict")
|
|
54
|
+
|
|
55
|
+
def _param_inputsize(self, gbase8sparams_list):
|
|
56
|
+
if hasattr(gbase8sparams_list[0], 'keys'): # [{k: gbase8sparams}, {}...]
|
|
57
|
+
sizes = {}
|
|
58
|
+
for params in gbase8sparams_list:
|
|
59
|
+
for k, value in params.items():
|
|
60
|
+
if value.input_size:
|
|
61
|
+
sizes[k] = value.input_size
|
|
62
|
+
else:
|
|
63
|
+
sizes = [None] * len(gbase8sparams_list[0])
|
|
64
|
+
for params in gbase8sparams_list:
|
|
65
|
+
for i, value in enumerate(params):
|
|
66
|
+
if value.input_size:
|
|
67
|
+
sizes[i] = value.input_size
|
|
68
|
+
return sizes
|
|
69
|
+
|
|
70
|
+
def _format_sql(self, sql, params):
|
|
71
|
+
"""replace %s to ? on sql,if mapping then replace to :named """
|
|
72
|
+
if not params:
|
|
73
|
+
return FORMAT_QMARK_REGEX.sub("?", sql).replace("%%", "%")
|
|
74
|
+
elif isinstance(params, (tuple, list)):
|
|
75
|
+
sql = sql % tuple('?' * len(params))
|
|
76
|
+
elif isinstance(params, Mapping):
|
|
77
|
+
sql = sql % {name: ":{name}".format(name=name) for name in params}
|
|
78
|
+
else:
|
|
79
|
+
raise TypeError("params must be tuple or dict")
|
|
80
|
+
return sql
|
|
81
|
+
|
|
82
|
+
def execute(self, sql, params=None):
|
|
83
|
+
|
|
84
|
+
if params is None:
|
|
85
|
+
return self.cursor.execute(sql)
|
|
86
|
+
sql = self._format_sql(sql, params)
|
|
87
|
+
gbase8sparams = self._format_params(params)
|
|
88
|
+
params = self._param_generator(gbase8sparams)
|
|
89
|
+
input_sizes = self._param_inputsize([gbase8sparams])
|
|
90
|
+
if isinstance(input_sizes, (list, tuple)):
|
|
91
|
+
self.cursor.setinputsizes(*input_sizes)
|
|
92
|
+
elif isinstance(input_sizes, dict):
|
|
93
|
+
self.cursor.setinputsizes(**input_sizes)
|
|
94
|
+
cursor = self.cursor.execute(sql, params)
|
|
95
|
+
return cursor
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def executemany(self, sql, params_list=()):
|
|
99
|
+
if not params_list:
|
|
100
|
+
return None
|
|
101
|
+
params_list = list(params_list)
|
|
102
|
+
sql = self._format_sql(sql, params_list[0])
|
|
103
|
+
|
|
104
|
+
new_params_list = []
|
|
105
|
+
gbase8sparams_list = []
|
|
106
|
+
for params in params_list:
|
|
107
|
+
gbase8sparams = self._format_params(params)
|
|
108
|
+
gbase8sparams_list.append(gbase8sparams)
|
|
109
|
+
params = self._param_generator(gbase8sparams)
|
|
110
|
+
new_params_list.append(params)
|
|
111
|
+
input_sizes = self._param_inputsize(gbase8sparams_list)
|
|
112
|
+
if isinstance(input_sizes, (list, tuple)):
|
|
113
|
+
self.cursor.setinputsizes(*input_sizes)
|
|
114
|
+
elif isinstance(input_sizes, dict):
|
|
115
|
+
self.cursor.setinputsizes(**input_sizes)
|
|
116
|
+
return self.cursor.executemany(sql, new_params_list)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def lastrowid(self):
|
|
120
|
+
return self.cursor.lastrowid
|
|
121
|
+
|
|
122
|
+
def __getattr__(self, attr):
|
|
123
|
+
return getattr(self.cursor, attr)
|
|
124
|
+
|
|
125
|
+
def __iter__(self):
|
|
126
|
+
return iter(self.cursor)
|
|
127
|
+
|
|
128
|
+
def close(self):
|
|
129
|
+
try:
|
|
130
|
+
self.cursor.close()
|
|
131
|
+
except Database.InterfaceError:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DatabaseWrapper(BaseDatabaseWrapper):
|
|
137
|
+
vendor = 'gbase8s'
|
|
138
|
+
display_name = 'GBase 8s'
|
|
139
|
+
Database = Database
|
|
140
|
+
SchemaEditorClass = DatabaseSchemaEditor
|
|
141
|
+
client_class = DatabaseClient
|
|
142
|
+
creation_class = DatabaseCreation
|
|
143
|
+
features_class = DatabaseFeatures
|
|
144
|
+
introspection_class = DatabaseIntrospection
|
|
145
|
+
ops_class = DatabaseOperations
|
|
146
|
+
isolation_levels = [
|
|
147
|
+
"DIRTY READ",
|
|
148
|
+
"COMMITTED READ LAST COMMITTED",
|
|
149
|
+
"COMMITTED READ",
|
|
150
|
+
"CURSOR STABILITY",
|
|
151
|
+
"REPEATABLE READ",
|
|
152
|
+
]
|
|
153
|
+
_limited_data_types = ("clob", "nclob", "blob")
|
|
154
|
+
data_types = {
|
|
155
|
+
'AutoField': 'SERIAL',
|
|
156
|
+
'SmallAutoField': 'SERIAL',
|
|
157
|
+
'BigAutoField': 'BIGSERIAL',
|
|
158
|
+
'BinaryField': 'BLOB',
|
|
159
|
+
'BooleanField': 'NUMBER(1)',
|
|
160
|
+
'NullBooleanField': 'NUMBER(1)',
|
|
161
|
+
'CharField': 'VARCHAR(%(max_length)s)',
|
|
162
|
+
'CommaSeparatedIntegerField': 'VARCHAR(%(max_length)s)',
|
|
163
|
+
'DateField': 'TIMESTAMP(0)',
|
|
164
|
+
'DateTimeField': 'TIMESTAMP',
|
|
165
|
+
'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)',
|
|
166
|
+
'DurationField': 'INTERVAL DAY(9) TO SECOND(5)',
|
|
167
|
+
'FileField': 'VARCHAR(%(max_length)s)',
|
|
168
|
+
'FilePathField': 'VARCHAR(%(max_length)s)',
|
|
169
|
+
'FloatField': 'FLOAT',
|
|
170
|
+
'IntegerField': 'INTEGER',
|
|
171
|
+
'BigIntegerField': 'BIGINT',
|
|
172
|
+
'IPAddressField': 'VARCHAR(15)',
|
|
173
|
+
'GenericIPAddressField': 'VARCHAR(39)',
|
|
174
|
+
'OneToOneField': 'INTEGER',
|
|
175
|
+
'PositiveIntegerField': 'INTEGER',
|
|
176
|
+
'PositiveSmallIntegerField': 'INTEGER',
|
|
177
|
+
'SlugField': 'VARCHAR(%(max_length)s)',
|
|
178
|
+
'SmallIntegerField': 'INTEGER',
|
|
179
|
+
'TextField': 'NCLOB',
|
|
180
|
+
'TimeField': 'TIMESTAMP',
|
|
181
|
+
"URLField": "VARCHAR(%(max_length)s)",
|
|
182
|
+
'UUIDField': 'VARCHAR(32)',
|
|
183
|
+
}
|
|
184
|
+
data_type_check_constraints = {
|
|
185
|
+
"BooleanField": "%(qn_column)s IN (0,1)",
|
|
186
|
+
'NullBooleanField': '(%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL)',
|
|
187
|
+
"PositiveIntegerField": "%(qn_column)s >= 0",
|
|
188
|
+
"PositiveSmallIntegerField": "%(qn_column)s >= 0",
|
|
189
|
+
}
|
|
190
|
+
operators = {
|
|
191
|
+
"exact": "= %s",
|
|
192
|
+
"iexact": "= UPPER(%s)",
|
|
193
|
+
"contains": "LIKE %s ESCAPE '\\'",
|
|
194
|
+
"icontains": "LIKE UPPER(%s) ESCAPE '\\'",
|
|
195
|
+
"gt": "> %s",
|
|
196
|
+
"gte": ">= %s",
|
|
197
|
+
"lt": "< %s",
|
|
198
|
+
"lte": "<= %s",
|
|
199
|
+
"startswith": "LIKE %s ESCAPE '\\'",
|
|
200
|
+
"endswith": "LIKE %s ESCAPE '\\'",
|
|
201
|
+
"istartswith": "LIKE UPPER(%s) ESCAPE '\\'",
|
|
202
|
+
"iendswith": "LIKE UPPER(%s) ESCAPE '\\'",
|
|
203
|
+
}
|
|
204
|
+
pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\', '\\'), '%%', '\%%'), '_', '\_')"
|
|
205
|
+
pattern_ops = {
|
|
206
|
+
"contains": r"LIKE '%%' || {} || '%%' ESCAPE '\'",
|
|
207
|
+
"icontains": r"LIKE '%%' || UPPER({}) || '%%' ESCAPE '\'",
|
|
208
|
+
"startswith": r"LIKE {} || '%%' ESCAPE '\'",
|
|
209
|
+
"istartswith": r"LIKE UPPER({}) || '%%' ESCAPE '\'",
|
|
210
|
+
"endswith": r"LIKE '%%' || {} ESCAPE '\'",
|
|
211
|
+
"iendswith": r"LIKE '%%' || UPPER({}) ESCAPE '\'",
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def __init__(self, *args, **kwargs):
|
|
216
|
+
super().__init__(*args, **kwargs)
|
|
217
|
+
self.features = DatabaseFeatures(self)
|
|
218
|
+
self.ops = DatabaseOperations(self)
|
|
219
|
+
self.client = DatabaseClient(self)
|
|
220
|
+
self.creation = DatabaseCreation(self)
|
|
221
|
+
self.introspection = DatabaseIntrospection(self)
|
|
222
|
+
self.validation = DatabaseValidation(self)
|
|
223
|
+
self.isolation_level = 'COMMITTED READ LAST COMMITTED'
|
|
224
|
+
|
|
225
|
+
def get_database_version(self):
|
|
226
|
+
return (8, 8)
|
|
227
|
+
|
|
228
|
+
def get_connection_params(self):
|
|
229
|
+
conn_params = {
|
|
230
|
+
"user": self.settings_dict["USER"],
|
|
231
|
+
"password": self.settings_dict["PASSWORD"],
|
|
232
|
+
"dsn": dsn(self.settings_dict)
|
|
233
|
+
}
|
|
234
|
+
if 'ISOLATION_LEVEL' in self.settings_dict['OPTIONS']:
|
|
235
|
+
if self.settings_dict['OPTIONS']['ISOLATION_LEVEL'].upper() in self.isolation_levels:
|
|
236
|
+
self.isolation_level = self.settings_dict['OPTIONS']['ISOLATION_LEVEL']
|
|
237
|
+
else:
|
|
238
|
+
raise ImproperlyConfigured("Invalid isolation level, need {}".format(self.isolation_levels))
|
|
239
|
+
|
|
240
|
+
return conn_params
|
|
241
|
+
|
|
242
|
+
def get_new_connection(self, conn_params):
|
|
243
|
+
return Database.connect(**conn_params)
|
|
244
|
+
|
|
245
|
+
def init_connection_state(self):
|
|
246
|
+
if self.isolation_level:
|
|
247
|
+
with self.cursor() as cursor:
|
|
248
|
+
cursor.execute("SET ISOLATION TO {}".format(self.isolation_level))
|
|
249
|
+
|
|
250
|
+
def create_cursor(self, name=None):
|
|
251
|
+
return CursorWrapper(self.connection.cursor())
|
|
252
|
+
|
|
253
|
+
def _set_autocommit(self, autocommit):
|
|
254
|
+
with self.wrap_database_errors:
|
|
255
|
+
self.connection.autocommit = autocommit
|
|
256
|
+
|
|
257
|
+
def check_constraints(self, table_names=None):
|
|
258
|
+
with self.cursor() as cursor:
|
|
259
|
+
cursor.execute('SET CONSTRAINTS ALL IMMEDIATE')
|
|
260
|
+
cursor.execute('SET CONSTRAINTS ALL DEFERRED')
|
|
261
|
+
|
|
262
|
+
def enable_constraint_checking(self):
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
def disable_constraint_checking(self):
|
|
266
|
+
with self.cursor() as cursor:
|
|
267
|
+
cursor.execute('SET CONSTRAINTS ALL DEFERRED')
|
|
268
|
+
return True
|
|
269
|
+
|
|
270
|
+
def is_usable(self):
|
|
271
|
+
try:
|
|
272
|
+
self.connection.ping()
|
|
273
|
+
except Database.Error:
|
|
274
|
+
return False
|
|
275
|
+
else:
|
|
276
|
+
return True
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def _nodb_connection(self):
|
|
280
|
+
settings_dict = self.settings_dict.copy()
|
|
281
|
+
settings_dict['NAME'] = 'sysmaster'
|
|
282
|
+
nodb_connection = self.__class__(
|
|
283
|
+
settings_dict,
|
|
284
|
+
alias=NO_DB_ALIAS,
|
|
285
|
+
)
|
|
286
|
+
return nodb_connection
|
|
287
|
+
|
|
288
|
+
|
django_gbase8s/client.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
import shutil
|
|
3
|
+
from django.db.backends.base.client import BaseDatabaseClient
|
|
4
|
+
|
|
5
|
+
class DatabaseClient(BaseDatabaseClient):
|
|
6
|
+
executable_name = "dbaccess"
|
|
7
|
+
wrapper_name = "rlwrap"
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def settings_to_cmd_args_env(cls, settings_dict, parameters):
|
|
11
|
+
server_name = settings_dict.get("SERVER_NAME")
|
|
12
|
+
db_name = settings_dict.get("NAME")
|
|
13
|
+
args = [cls.executable_name, db_name, '-']
|
|
14
|
+
wrapper_path = shutil.which(cls.wrapper_name)
|
|
15
|
+
if wrapper_path:
|
|
16
|
+
args = [wrapper_path] + args
|
|
17
|
+
args.extend(parameters)
|
|
18
|
+
return args, {'GBASEDBTSERVER': server_name}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from django.db.models.sql import compiler
|
|
3
|
+
from django.db.transaction import TransactionManagementError
|
|
4
|
+
from django.db.utils import DatabaseError, NotSupportedError
|
|
5
|
+
from django.core.exceptions import EmptyResultSet
|
|
6
|
+
from django.db.models.expressions import (
|
|
7
|
+
Ref,
|
|
8
|
+
Exists
|
|
9
|
+
)
|
|
10
|
+
from django.utils.deprecation import (
|
|
11
|
+
RemovedInDjango30Warning, RemovedInDjango31Warning,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def as_gbase8s_for_exists(self, compiler, connection, template=None, **extra_context):
|
|
15
|
+
sql, params = self.as_sql(compiler, connection, template, **extra_context)
|
|
16
|
+
sql = 'CASE WHEN {} THEN 1 ELSE 0 END'.format(sql)
|
|
17
|
+
return sql, params
|
|
18
|
+
|
|
19
|
+
setattr(Exists, 'as_gbase8s', as_gbase8s_for_exists)
|
|
20
|
+
|
|
21
|
+
class SQLCompiler(compiler.SQLCompiler):
|
|
22
|
+
|
|
23
|
+
def get_group_by(self, select, order_by):
|
|
24
|
+
"""
|
|
25
|
+
Returns a list of 2-tuples of form (sql, params).
|
|
26
|
+
|
|
27
|
+
The logic of what exactly the GROUP BY clause contains is hard
|
|
28
|
+
to describe in other words than "if it passes the test suite,
|
|
29
|
+
then it is correct".
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
if self.query.group_by is None:
|
|
33
|
+
return []
|
|
34
|
+
expressions = []
|
|
35
|
+
group_by_refs = set()
|
|
36
|
+
if self.query.group_by is not True:
|
|
37
|
+
for expr in self.query.group_by:
|
|
38
|
+
if not hasattr(expr, 'as_sql'):
|
|
39
|
+
expressions.append(self.query.resolve_ref(expr))
|
|
40
|
+
if isinstance(expr, Ref):
|
|
41
|
+
if expr.refs not in group_by_refs:
|
|
42
|
+
group_by_refs.add(expr.name)
|
|
43
|
+
expressions.append(expr.source)
|
|
44
|
+
else:
|
|
45
|
+
expressions.append(expr)
|
|
46
|
+
# Note that even if the group_by is set, it is only the minimal
|
|
47
|
+
# set to group by. So, we need to add cols in select, order_by, and
|
|
48
|
+
# having into the select in any case.
|
|
49
|
+
selected_expr_positions = {}
|
|
50
|
+
for ordinal, (expr, _, alias) in enumerate(select, start=1):
|
|
51
|
+
if alias:
|
|
52
|
+
selected_expr_positions[expr] = ordinal
|
|
53
|
+
if alias in group_by_refs:
|
|
54
|
+
continue
|
|
55
|
+
expressions.extend(expr.get_group_by_cols())
|
|
56
|
+
# cols = expr.get_group_by_cols()
|
|
57
|
+
# for col in cols:
|
|
58
|
+
# expressions.append(col)
|
|
59
|
+
for expr, (sql, params, is_ref) in order_by:
|
|
60
|
+
if expr.contains_aggregate:
|
|
61
|
+
continue
|
|
62
|
+
# We can skip References to select clause, as all expressions in
|
|
63
|
+
# the select clause are already part of the group by.
|
|
64
|
+
if is_ref:
|
|
65
|
+
continue
|
|
66
|
+
expressions.extend(expr.get_source_expressions())
|
|
67
|
+
having_group_by = self.having.get_group_by_cols() if self.having else ()
|
|
68
|
+
for expr in having_group_by:
|
|
69
|
+
expressions.append(expr)
|
|
70
|
+
result = []
|
|
71
|
+
seen = set()
|
|
72
|
+
expressions = self.collapse_group_by(expressions, having_group_by)
|
|
73
|
+
|
|
74
|
+
allows_group_by_select_index = (
|
|
75
|
+
self.connection.features.allows_group_by_select_index
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
for expr in expressions:
|
|
79
|
+
sql, params = self.compile(expr)
|
|
80
|
+
if (
|
|
81
|
+
allows_group_by_select_index
|
|
82
|
+
and selected_expr_positions.get(expr)
|
|
83
|
+
):
|
|
84
|
+
sql, params = str(selected_expr_positions.get(expr)), ()
|
|
85
|
+
if (sql, tuple(params)) not in seen:
|
|
86
|
+
result.append((sql, params))
|
|
87
|
+
seen.add((sql, tuple(params)))
|
|
88
|
+
return result
|
|
89
|
+
def as_sql(self, with_limits=True, with_col_aliases=False):
|
|
90
|
+
"""
|
|
91
|
+
Creates the SQL for this query. Returns the SQL string and list of
|
|
92
|
+
parameters.
|
|
93
|
+
|
|
94
|
+
If 'with_limits' is False, any limit/offset information is not included
|
|
95
|
+
in the query.
|
|
96
|
+
"""
|
|
97
|
+
refcounts_before = self.query.alias_refcount.copy()
|
|
98
|
+
try:
|
|
99
|
+
extra_select, order_by, group_by = self.pre_sql_setup()
|
|
100
|
+
for_update_part = None
|
|
101
|
+
# Is a LIMIT/OFFSET clause needed?
|
|
102
|
+
with_limit_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark)
|
|
103
|
+
combinator = self.query.combinator
|
|
104
|
+
features = self.connection.features
|
|
105
|
+
if combinator:
|
|
106
|
+
if not getattr(features, 'supports_select_{}'.format(combinator)):
|
|
107
|
+
raise NotSupportedError('{} is not supported on this database backend.'.format(combinator))
|
|
108
|
+
result, params = self.get_combinator_sql(combinator, self.query.combinator_all)
|
|
109
|
+
else:
|
|
110
|
+
distinct_fields, distinct_params = self.get_distinct()
|
|
111
|
+
# This must come after 'select', 'ordering', and 'distinct'
|
|
112
|
+
# (see docstring of get_from_clause() for details).
|
|
113
|
+
from_, f_params = self.get_from_clause()
|
|
114
|
+
where, w_params = self.compile(self.where) if self.where is not None else ("", [])
|
|
115
|
+
having, h_params = self.compile(self.having) if self.having is not None else ("", [])
|
|
116
|
+
result = ['SELECT']
|
|
117
|
+
params = []
|
|
118
|
+
|
|
119
|
+
if self.query.distinct:
|
|
120
|
+
distinct_result, distinct_params = self.connection.ops.distinct_sql(
|
|
121
|
+
distinct_fields,
|
|
122
|
+
distinct_params,
|
|
123
|
+
)
|
|
124
|
+
result += distinct_result
|
|
125
|
+
params += distinct_params
|
|
126
|
+
|
|
127
|
+
out_cols = []
|
|
128
|
+
col_idx = 1
|
|
129
|
+
for _, (s_sql, s_params), alias in self.select + extra_select:
|
|
130
|
+
if alias:
|
|
131
|
+
s_sql = '%s AS %s' % (s_sql, self.connection.ops.quote_name(alias))
|
|
132
|
+
elif with_col_aliases:
|
|
133
|
+
s_sql = '%s AS %s' % (s_sql, 'Col%d' % col_idx)
|
|
134
|
+
col_idx += 1
|
|
135
|
+
params.extend(s_params)
|
|
136
|
+
out_cols.append(s_sql)
|
|
137
|
+
|
|
138
|
+
# result.append(', '.join(out_cols))
|
|
139
|
+
# result.append('FROM')
|
|
140
|
+
# result.extend(from_)
|
|
141
|
+
result += [', '.join(out_cols), 'FROM', *from_]
|
|
142
|
+
params.extend(f_params)
|
|
143
|
+
|
|
144
|
+
if self.query.select_for_update and self.connection.features.has_select_for_update:
|
|
145
|
+
if self.connection.get_autocommit():
|
|
146
|
+
raise TransactionManagementError('select_for_update cannot be used outside of a transaction.')
|
|
147
|
+
|
|
148
|
+
if with_limit_offset and not self.connection.features.supports_select_for_update_with_limit:
|
|
149
|
+
raise NotSupportedError(
|
|
150
|
+
'LIMIT/OFFSET is not supported with '
|
|
151
|
+
'select_for_update on this database backend.'
|
|
152
|
+
)
|
|
153
|
+
nowait = self.query.select_for_update_nowait
|
|
154
|
+
skip_locked = self.query.select_for_update_skip_locked
|
|
155
|
+
of = self.query.select_for_update_of
|
|
156
|
+
# If it's a NOWAIT/SKIP LOCKED/OF query but the backend
|
|
157
|
+
# doesn't support it, raise NotSupportedError to prevent a
|
|
158
|
+
# possible deadlock.
|
|
159
|
+
if nowait and not self.connection.features.has_select_for_update_nowait:
|
|
160
|
+
raise NotSupportedError('NOWAIT is not supported on this database backend.')
|
|
161
|
+
elif skip_locked and not self.connection.features.has_select_for_update_skip_locked:
|
|
162
|
+
raise NotSupportedError('SKIP LOCKED is not supported on this database backend.')
|
|
163
|
+
elif of and not self.connection.features.has_select_for_update_of:
|
|
164
|
+
raise NotSupportedError('FOR UPDATE OF is not supported on this database backend.')
|
|
165
|
+
for_update_part = self.connection.ops.for_update_sql(
|
|
166
|
+
nowait=nowait,
|
|
167
|
+
skip_locked=skip_locked,
|
|
168
|
+
of=self.get_select_for_update_of_arguments(),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if for_update_part and self.connection.features.for_update_after_from:
|
|
172
|
+
result.append(for_update_part)
|
|
173
|
+
|
|
174
|
+
if where:
|
|
175
|
+
result.append('WHERE %s' % where)
|
|
176
|
+
params.extend(w_params)
|
|
177
|
+
|
|
178
|
+
grouping = []
|
|
179
|
+
for g_sql, g_params in group_by:
|
|
180
|
+
grouping.append(g_sql)
|
|
181
|
+
params.extend(g_params)
|
|
182
|
+
if grouping:
|
|
183
|
+
if distinct_fields:
|
|
184
|
+
raise NotImplementedError('annotate() + distinct(fields) is not implemented.')
|
|
185
|
+
order_by = order_by or self.connection.ops.force_no_ordering()
|
|
186
|
+
result.append('GROUP BY %s' % ', '.join(grouping))
|
|
187
|
+
if self._meta_ordering:
|
|
188
|
+
# When the deprecation ends, replace with:
|
|
189
|
+
# order_by = None
|
|
190
|
+
warnings.warn(
|
|
191
|
+
"%s QuerySet won't use Meta.ordering in Django 3.1. "
|
|
192
|
+
"Add .order_by(%s) to retain the current query." % (
|
|
193
|
+
self.query.model.__name__,
|
|
194
|
+
', '.join(repr(f) for f in self._meta_ordering),
|
|
195
|
+
),
|
|
196
|
+
RemovedInDjango31Warning,
|
|
197
|
+
stacklevel=4,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if having:
|
|
201
|
+
result.append('HAVING %s' % having)
|
|
202
|
+
params.extend(h_params)
|
|
203
|
+
|
|
204
|
+
if self.query.explain_query:
|
|
205
|
+
result.insert(0, self.connection.ops.explain_query_prefix(
|
|
206
|
+
self.query.explain_format,
|
|
207
|
+
**self.query.explain_options
|
|
208
|
+
))
|
|
209
|
+
|
|
210
|
+
if order_by:
|
|
211
|
+
ordering = []
|
|
212
|
+
for _, (o_sql, o_params, _) in order_by:
|
|
213
|
+
ordering.append(o_sql)
|
|
214
|
+
params.extend(o_params)
|
|
215
|
+
result.append('ORDER BY %s' % ', '.join(ordering))
|
|
216
|
+
|
|
217
|
+
if with_limit_offset:
|
|
218
|
+
if len(result) == 1:
|
|
219
|
+
result = ["SELECT","* FROM (", result[0], ")"]
|
|
220
|
+
result.insert(1,
|
|
221
|
+
self.connection.ops.limit_offset_sql(
|
|
222
|
+
self.query.low_mark,
|
|
223
|
+
self.query.high_mark
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if for_update_part and not self.connection.features.for_update_after_from:
|
|
228
|
+
result.append(for_update_part)
|
|
229
|
+
if self.query.subquery and extra_select:
|
|
230
|
+
# If the query is used as a subquery, the extra selects would
|
|
231
|
+
# result in more columns than the left-hand side expression is
|
|
232
|
+
# expecting. This can happen when a subquery uses a combination
|
|
233
|
+
# of order_by() and distinct(), forcing the ordering expressions
|
|
234
|
+
# to be selected as well. Wrap the query in another subquery
|
|
235
|
+
# to exclude extraneous selects.
|
|
236
|
+
sub_selects = []
|
|
237
|
+
sub_params = []
|
|
238
|
+
for index, (select, _, alias) in enumerate(self.select, start=1):
|
|
239
|
+
if not alias and with_col_aliases:
|
|
240
|
+
alias = 'col%d' % index
|
|
241
|
+
if alias:
|
|
242
|
+
sub_selects.append("%s.%s" % (
|
|
243
|
+
self.connection.ops.quote_name('subquery'),
|
|
244
|
+
self.connection.ops.quote_name(alias),
|
|
245
|
+
))
|
|
246
|
+
else:
|
|
247
|
+
select_clone = select.relabeled_clone({select.alias: 'subquery'})
|
|
248
|
+
subselect, subparams = select_clone.as_sql(self, self.connection)
|
|
249
|
+
sub_selects.append(subselect)
|
|
250
|
+
sub_params.extend(subparams)
|
|
251
|
+
return 'SELECT %s FROM (%s) subquery' % (
|
|
252
|
+
', '.join(sub_selects),
|
|
253
|
+
' '.join(result),
|
|
254
|
+
), tuple(sub_params + params)
|
|
255
|
+
|
|
256
|
+
return ' '.join(result), tuple(params)
|
|
257
|
+
finally:
|
|
258
|
+
# Finally do cleanup - get rid of the joins we created above.
|
|
259
|
+
self.query.reset_refcounts(refcounts_before)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
|
|
275
|
+
pass
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.db.backends.base.creation import BaseDatabaseCreation
|
|
2
|
+
|
|
3
|
+
class DatabaseCreation(BaseDatabaseCreation):
|
|
4
|
+
|
|
5
|
+
def sql_table_creation_suffix(self):
|
|
6
|
+
"""
|
|
7
|
+
SQL to append to the end of the test table creation statements.
|
|
8
|
+
"""
|
|
9
|
+
return "WITH LOG"
|
|
10
|
+
|
|
11
|
+
def _destroy_test_db(self, test_database_name, verbosity):
|
|
12
|
+
with self.connection._nodb_connection.cursor() as cursor:
|
|
13
|
+
cursor.execute("CLOSE DATABASE")
|
|
14
|
+
cursor.execute("DROP DATABASE %s"
|
|
15
|
+
% self.connection.ops.quote_name(test_database_name))
|