33
44from sql_server .pyodbc .compat import zip_longest
55
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-
146
157class SQLCompiler (compiler .SQLCompiler ):
16-
8+
179 def resolve_columns (self , row , fields = ()):
1810 index_start = len (list (self .query .extra_select .keys ()))
1911 values = [self .query .convert_values (v , None , connection = self .connection ) for v in row [:index_start ]]
2012 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 )
2216 return tuple (values )
2317
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 ):
5819 """
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.
6222
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.
6425 """
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 '' , ()
8128
82- self .modify_query ( strategy , ordering , out_cols )
29+ self .pre_sql_setup ( )
8330
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 )
8650
8751 # This must come after 'select' and 'ordering' -- see docstring of
8852 # get_from_clause() for details.
8953 from_ , f_params = self .get_from_clause ()
9054
9155 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 )
9459 params = []
9560 for val in self .query .extra_select .values ():
9661 params .extend (val [1 ])
9762
9863 result = ['SELECT' ]
64+
9965 if self .query .distinct :
10066 result .append ('DISTINCT' )
10167
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+
10288 result .append (', ' .join (out_cols + self .query .ordering_aliases ))
10389
90+ params .extend (offset_params )
91+
10492 result .append ('FROM' )
10593 result .extend (from_ )
10694 params .extend (f_params )
@@ -113,8 +101,7 @@ def _as_sql(self, strategy):
113101 grouping , gb_params = self .get_grouping (ordering_group_by )
114102 else :
115103 grouping , gb_params = self .get_grouping ()
116- if grouping :
117- if ordering :
104+ if grouping and ordering :
118105 # If the backend can't group by PK (i.e., any database
119106 # other than MySQL), then any fields mentioned in the
120107 # ordering clause needs to be in the group by clause.
@@ -123,7 +110,8 @@ def _as_sql(self, strategy):
123110 if col not in grouping :
124111 grouping .append (str (col ))
125112 gb_params .extend (col_params )
126- else :
113+ if grouping :
114+ if not ordering :
127115 ordering = self .connection .ops .force_no_ordering ()
128116 result .append ('GROUP BY %s' % ', ' .join (grouping ))
129117 params .extend (gb_params )
@@ -132,67 +120,54 @@ def _as_sql(self, strategy):
132120 result .append ('HAVING %s' % having )
133121 params .extend (h_params )
134122
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' )
174140
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 )
179144
180- return ' ' .join (result ), params
145+ return ' ' .join (result ), tuple ( params )
181146
182- def get_ordering (self ):
147+ def _get_ordering (self , out_cols , allow_aliases = True ):
183148 # 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 ()
185150 grouping = []
186- for t in ordering_group_by :
151+ for group_by in ordering_group_by :
187152 try :
188- int (t [0 ])
153+ col_index = int (group_by [0 ]) - 1
154+ grouping .append ((out_cols [col_index ], group_by [1 ]))
189155 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
196171
197172class SQLInsertCompiler (compiler .SQLInsertCompiler , SQLCompiler ):
198173
0 commit comments