3
3
4
4
from sql_server .pyodbc .compat import zip_longest
5
5
6
- REV_ODIR = {
7
- 'ASC' : 'DESC' ,
8
- 'DESC' : 'ASC'
9
- }
10
-
11
- # Strategies for handling limit+offset emulation:
12
- USE_ROW_NUMBER = 0 # For SQL Server >= 2005
13
-
14
6
15
7
class SQLCompiler (compiler .SQLCompiler ):
16
-
8
+
17
9
def resolve_columns (self , row , fields = ()):
18
10
index_start = len (list (self .query .extra_select .keys ()))
19
11
values = [self .query .convert_values (v , None , connection = self .connection ) for v in row [:index_start ]]
20
12
for value , field in zip_longest (row [index_start :], fields ):
21
- values .append (self .query .convert_values (value , field , connection = self .connection ))
13
+ if field :
14
+ value = self .query .convert_values (value , field , connection = self .connection )
15
+ values .append (value )
22
16
return tuple (values )
23
17
24
- def modify_query (self , strategy , ordering , out_cols ):
25
- """
26
- Helper method, called from _as_sql()
27
-
28
- Sets the value of the self._ord and self.default_reverse_ordering
29
- attributes.
30
- Can modify the values of the out_cols list argument and the
31
- self.query.ordering_aliases attribute.
32
- """
33
- self .default_reverse_ordering = False
34
- self ._ord = []
35
- cnt = 0
36
- extra_select_aliases = [k .strip ('[]' ) for k in self .query .extra_select .keys ()]
37
- for ord_spec_item in ordering :
38
- if ord_spec_item .endswith (' ASC' ) or ord_spec_item .endswith (' DESC' ):
39
- parts = ord_spec_item .split ()
40
- col , odir = ' ' .join (parts [:- 1 ]), parts [- 1 ]
41
- if col not in self .query .ordering_aliases and col .strip ('[]' ) not in extra_select_aliases :
42
- if col .isdigit ():
43
- cnt += 1
44
- n = int (col )- 1
45
- alias = 'OrdAlias%d' % cnt
46
- # ordering by aliases defined in the same query is not available ...
47
- self ._ord .append ((out_cols [n ], odir ))
48
- out_cols [n ] = '%s AS [%s]' % (out_cols [n ], alias )
49
- else :
50
- self ._ord .append ((col , odir ))
51
- else :
52
- self ._ord .append ((col , odir ))
53
-
54
- if not self ._ord and 'RAND()' in ordering :
55
- self ._ord .append (('RAND()' ,'' ))
56
-
57
- def _as_sql (self , strategy ):
18
+ def as_sql (self , with_limits = True , with_col_aliases = False ):
58
19
"""
59
- Helper method, called from as_sql()
60
- Similar to django/db/models/sql/query.py:Query.as_sql() but without
61
- the ordering and limits code.
20
+ Creates the SQL for this query. Returns the SQL string and list of
21
+ parameters.
62
22
63
- Returns SQL that hasn't an order-by clause.
23
+ If 'with_limits' is False, any limit/offset information is not included
24
+ in the query.
64
25
"""
65
- # get_columns needs to be called before get_ordering to populate
66
- # _select_alias.
67
- out_cols = self .get_columns (True )
68
- ordering , ordering_group_by = self .get_ordering ()
69
- if not ordering :
70
- meta = self .query .get_meta ()
71
- qn = self .quote_name_unless_alias
72
- # Special case: pk not in out_cols, use random ordering.
73
- #
74
- if '%s.%s' % (qn (meta .db_table ), qn (meta .pk .db_column or meta .pk .column )) not in self .get_columns ():
75
- ordering = ['RAND()' ]
76
- # XXX: Maybe use group_by field for ordering?
77
- #if self.group_by:
78
- #ordering = ['%s.%s ASC' % (qn(self.group_by[0][0]),qn(self.group_by[0][1]))]
79
- else :
80
- ordering = ['%s.%s ASC' % (qn (meta .db_table ), qn (meta .pk .db_column or meta .pk .column ))]
26
+ if with_limits and self .query .low_mark == self .query .high_mark :
27
+ return '' , ()
81
28
82
- self .modify_query ( strategy , ordering , out_cols )
29
+ self .pre_sql_setup ( )
83
30
84
- order = ', ' .join (['%s %s' % pair for pair in self ._ord ])
85
- self .query .ordering_aliases .append ('(ROW_NUMBER() OVER (ORDER BY %s)) AS [rn]' % order )
31
+ # The do_offset flag indicates whether we need to construct
32
+ # the SQL needed to use limit/offset w/SQL Server.
33
+ high_mark = self .query .high_mark
34
+ low_mark = self .query .low_mark
35
+ do_limit = with_limits and high_mark is not None
36
+ do_offset = with_limits and low_mark != 0
37
+ # SQL Server 2012 or newer supports OFFSET/FETCH clause
38
+ supports_offset_clause = self .connection .ops .sql_server_ver >= 2012
39
+
40
+ # After executing the query, we must get rid of any joins the query
41
+ # setup created. So, take note of alias counts before the query ran.
42
+ # However we do not want to get rid of stuff done in pre_sql_setup(),
43
+ # as the pre_sql_setup will modify query state in a way that forbids
44
+ # another run of it.
45
+ if self .connection ._DJANGO_VERSION >= 14 :
46
+ self .refcounts_before = self .query .alias_refcount .copy ()
47
+ out_cols = self .get_columns (with_col_aliases )
48
+ ordering , ordering_group_by , offset_params = \
49
+ self ._get_ordering (out_cols , supports_offset_clause or not do_offset )
86
50
87
51
# This must come after 'select' and 'ordering' -- see docstring of
88
52
# get_from_clause() for details.
89
53
from_ , f_params = self .get_from_clause ()
90
54
91
55
qn = self .quote_name_unless_alias
92
- where , w_params = self .query .where .as_sql (qn , self .connection )
93
- having , h_params = self .query .having .as_sql (qn , self .connection )
56
+
57
+ where , w_params = self .query .where .as_sql (qn = qn , connection = self .connection )
58
+ having , h_params = self .query .having .as_sql (qn = qn , connection = self .connection )
94
59
params = []
95
60
for val in self .query .extra_select .values ():
96
61
params .extend (val [1 ])
97
62
98
63
result = ['SELECT' ]
64
+
99
65
if self .query .distinct :
100
66
result .append ('DISTINCT' )
101
67
68
+ if do_offset :
69
+ if not ordering :
70
+ meta = self .query .get_meta ()
71
+ qn = self .quote_name_unless_alias
72
+ pk = '%s.%s' % (qn (meta .db_table ), qn (meta .pk .db_column or meta .pk .column ))
73
+ # Special case: pk not in out_cols, use random ordering.
74
+ if not pk in out_cols :
75
+ ordering = [self .connection .ops .random_function_sql ()]
76
+ # XXX: Maybe use group_by field for ordering?
77
+ #if self.group_by:
78
+ #ordering = ['%s.%s ASC' % (qn(self.group_by[0][0]),qn(self.group_by[0][1]))]
79
+ else :
80
+ ordering = ['%s ASC' % pk ]
81
+ if not supports_offset_clause :
82
+ order = ', ' .join (ordering )
83
+ self .query .ordering_aliases .append ('(ROW_NUMBER() OVER (ORDER BY %s)) AS [rn]' % order )
84
+ ordering = self .connection .ops .force_no_ordering ()
85
+ elif do_limit :
86
+ result .append ('TOP %d' % high_mark )
87
+
102
88
result .append (', ' .join (out_cols + self .query .ordering_aliases ))
103
89
90
+ params .extend (offset_params )
91
+
104
92
result .append ('FROM' )
105
93
result .extend (from_ )
106
94
params .extend (f_params )
@@ -113,8 +101,7 @@ def _as_sql(self, strategy):
113
101
grouping , gb_params = self .get_grouping (ordering_group_by )
114
102
else :
115
103
grouping , gb_params = self .get_grouping ()
116
- if grouping :
117
- if ordering :
104
+ if grouping and ordering :
118
105
# If the backend can't group by PK (i.e., any database
119
106
# other than MySQL), then any fields mentioned in the
120
107
# ordering clause needs to be in the group by clause.
@@ -123,7 +110,8 @@ def _as_sql(self, strategy):
123
110
if col not in grouping :
124
111
grouping .append (str (col ))
125
112
gb_params .extend (col_params )
126
- else :
113
+ if grouping :
114
+ if not ordering :
127
115
ordering = self .connection .ops .force_no_ordering ()
128
116
result .append ('GROUP BY %s' % ', ' .join (grouping ))
129
117
params .extend (gb_params )
@@ -132,67 +120,54 @@ def _as_sql(self, strategy):
132
120
result .append ('HAVING %s' % having )
133
121
params .extend (h_params )
134
122
135
- return ' ' .join (result ), tuple (params )
136
-
137
- def as_sql (self , with_limits = True , with_col_aliases = False ):
138
- """
139
- Creates the SQL for this query. Returns the SQL string and list of
140
- parameters.
141
-
142
- If 'with_limits' is False, any limit/offset information is not included
143
- in the query.
144
- """
145
- if with_limits and self .query .low_mark == self .query .high_mark :
146
- return '' , ()
147
-
148
- # The do_offset flag indicates whether we need to construct
149
- # the SQL needed to use limit/offset w/SQL Server.
150
- do_offset = with_limits and (self .query .high_mark is not None or self .query .low_mark != 0 )
151
-
152
- # no row ordering or row offsetting is assumed to be required
153
- # if the result type is specified as SINGLE
154
- if hasattr (self , 'result_type' ) and self .result_type == SINGLE :
155
- do_offset = False
156
-
157
- # If no offsets, just return the result of the base class
158
- # `as_sql`.
159
- if not do_offset :
160
- return super (SQLCompiler , self ).as_sql (with_limits = False ,
161
- with_col_aliases = with_col_aliases )
162
- # Shortcut for the corner case when high_mark value is 0:
163
- if self .query .high_mark == 0 :
164
- return "" , ()
165
-
166
- self .pre_sql_setup ()
167
-
168
- # SQL Server 2005 or newer
169
- sql , params = self ._as_sql (USE_ROW_NUMBER )
170
-
171
- # Construct the final SQL clause, using the initial select SQL
172
- # obtained above.
173
- result = ['SELECT * FROM (%s) AS X' % sql ]
123
+ if ordering and not with_col_aliases :
124
+ result .append ('ORDER BY %s' % ', ' .join (ordering ))
125
+ if do_offset and supports_offset_clause :
126
+ result .append ('OFFSET %d ROWS' % low_mark )
127
+ if do_limit :
128
+ result .append ('FETCH FIRST %d ROWS ONLY' % (high_mark - low_mark ))
129
+
130
+ if do_offset and not supports_offset_clause :
131
+ # Construct the final SQL clause, using the initial select SQL
132
+ # obtained above.
133
+ result = ['SELECT * FROM (%s) AS X WHERE X.rn' % ' ' .join (result )]
134
+ # Place WHERE condition on `rn` for the desired range.
135
+ if do_limit :
136
+ result .append ('BETWEEN %d AND %d' % (low_mark + 1 , high_mark ))
137
+ else :
138
+ result .append ('>= %d' % (low_mark + 1 ))
139
+ result .append ('ORDER BY X.rn' )
174
140
175
- # Place WHERE condition on `rn` for the desired range.
176
- if self .query .high_mark is None :
177
- self .query .high_mark = 9223372036854775807
178
- result .append ('WHERE X.rn BETWEEN %d AND %d' % (self .query .low_mark + 1 , self .query .high_mark ))
141
+ # Finally do cleanup - get rid of the joins we created above.
142
+ if self .connection ._DJANGO_VERSION >= 14 :
143
+ self .query .reset_refcounts (self .refcounts_before )
179
144
180
- return ' ' .join (result ), params
145
+ return ' ' .join (result ), tuple ( params )
181
146
182
- def get_ordering (self ):
147
+ def _get_ordering (self , out_cols , allow_aliases = True ):
183
148
# SQL Server doesn't support grouping by column number
184
- ordering , ordering_group_by = super ( SQLCompiler , self ) .get_ordering ()
149
+ ordering , ordering_group_by = self .get_ordering ()
185
150
grouping = []
186
- for t in ordering_group_by :
151
+ for group_by in ordering_group_by :
187
152
try :
188
- int (t [0 ])
153
+ col_index = int (group_by [0 ]) - 1
154
+ grouping .append ((out_cols [col_index ], group_by [1 ]))
189
155
except ValueError :
190
- grouping .append (t )
191
- return ordering , grouping
192
-
193
- def execute_sql (self , result_type = MULTI ):
194
- self .result_type = result_type
195
- return super (SQLCompiler , self ).execute_sql (result_type )
156
+ grouping .append (group_by )
157
+ # value_expression in OVER clause cannot refer to
158
+ # expressions or aliases in the select list. See:
159
+ # http://msdn.microsoft.com/en-us/library/ms189461.aspx
160
+ offset_params = []
161
+ if not allow_aliases :
162
+ keys = self .query .extra .keys ()
163
+ for i in range (len (ordering )):
164
+ order_col , order_dir = ordering [i ].split ()
165
+ order_col = order_col .strip ('[]' )
166
+ if order_col in keys :
167
+ ex = self .query .extra [order_col ]
168
+ ordering [i ] = '%s %s' % (ex [0 ], order_dir )
169
+ offset_params .extend (ex [1 ])
170
+ return ordering , grouping , offset_params
196
171
197
172
class SQLInsertCompiler (compiler .SQLInsertCompiler , SQLCompiler ):
198
173
0 commit comments