Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

queryset-refactor: Initial pass at fixing the Oracle support. Thanks,…

… Justin Bronn. Fixed #6161

This is untested (by me) and is a slight modification on Justin's original
patch, so feedback and bug reports are welcome.


git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7321 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 22bb040b60c868b77c956333ccbd3c07eb342487 1 parent 9ee77a7
Malcolm Tredinnick authored March 19, 2008

Showing 1 changed file with 65 additions and 186 deletions. Show diff stats Hide diff stats

  1. 251  django/db/backends/oracle/base.py
251  django/db/backends/oracle/base.py
@@ -25,6 +25,7 @@
25 25
 class DatabaseFeatures(BaseDatabaseFeatures):
26 26
     allows_group_by_ordinal = False
27 27
     allows_unique_and_pk = False        # Suppress UNIQUE/PK for Oracle (ORA-02259)
  28
+    empty_fetchmany_value = ()
28 29
     needs_datetime_string_cast = False
29 30
     needs_upper_for_iops = True
30 31
     supports_tablespaces = True
@@ -99,195 +100,13 @@ def max_name_length(self):
99 100
         return 30
100 101
 
101 102
     def query_set_class(self, DefaultQuerySet):
102  
-        from django.db import connection
103  
-        from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word
104  
-
105  
-        class OracleQuerySet(DefaultQuerySet):
106  
-
107  
-            def iterator(self):
108  
-                "Performs the SELECT database lookup of this QuerySet."
109  
-
110  
-                from django.db.models.query import get_cached_row
111  
-
112  
-                # self._select is a dictionary, and dictionaries' key order is
113  
-                # undefined, so we convert it to a list of tuples.
114  
-                extra_select = self._select.items()
115  
-
116  
-                full_query = None
117  
-
118  
-                try:
119  
-                    try:
120  
-                        select, sql, params, full_query = self._get_sql_clause(get_full_query=True)
121  
-                    except TypeError:
122  
-                        select, sql, params = self._get_sql_clause()
123  
-                except EmptyResultSet:
124  
-                    raise StopIteration
125  
-                if not full_query:
126  
-                    full_query = "SELECT %s%s\n%s" % ((self._distinct and "DISTINCT " or ""), ', '.join(select), sql)
127  
-
128  
-                cursor = connection.cursor()
129  
-                cursor.execute(full_query, params)
130  
-
131  
-                fill_cache = self._select_related
132  
-                fields = self.model._meta.fields
133  
-                index_end = len(fields)
134  
-
135  
-                # so here's the logic;
136  
-                # 1. retrieve each row in turn
137  
-                # 2. convert NCLOBs
138  
-
139  
-                while 1:
140  
-                    rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
141  
-                    if not rows:
142  
-                        raise StopIteration
143  
-                    for row in rows:
144  
-                        row = self.resolve_columns(row, fields)
145  
-                        if fill_cache:
146  
-                            obj, index_end = get_cached_row(klass=self.model, row=row,
147  
-                                                            index_start=0, max_depth=self._max_related_depth)
148  
-                        else:
149  
-                            obj = self.model(*row[:index_end])
150  
-                        for i, k in enumerate(extra_select):
151  
-                            setattr(obj, k[0], row[index_end+i])
152  
-                        yield obj
153  
-
154  
-
155  
-            def _get_sql_clause(self, get_full_query=False):
156  
-                from django.db.models.query import fill_table_cache, \
157  
-                    handle_legacy_orderlist, orderfield2column
158  
-
159  
-                opts = self.model._meta
160  
-                qn = connection.ops.quote_name
161  
-
162  
-                # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
163  
-                select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
164  
-                tables = [quote_only_if_word(t) for t in self._tables]
165  
-                joins = SortedDict()
166  
-                where = self._where[:]
167  
-                params = self._params[:]
168  
-
169  
-                # Convert self._filters into SQL.
170  
-                joins2, where2, params2 = self._filters.get_sql(opts)
171  
-                joins.update(joins2)
172  
-                where.extend(where2)
173  
-                params.extend(params2)
174  
-
175  
-                # Add additional tables and WHERE clauses based on select_related.
176  
-                if self._select_related:
177  
-                    fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
178  
-
179  
-                # Add any additional SELECTs.
180  
-                if self._select:
181  
-                    select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()])
182  
-
183  
-                # Start composing the body of the SQL statement.
184  
-                sql = [" FROM", qn(opts.db_table)]
185  
-
186  
-                # Compose the join dictionary into SQL describing the joins.
187  
-                if joins:
188  
-                    sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition)
189  
-                                    for (alias, (table, join_type, condition)) in joins.items()]))
190  
-
191  
-                # Compose the tables clause into SQL.
192  
-                if tables:
193  
-                    sql.append(", " + ", ".join(tables))
194  
-
195  
-                # Compose the where clause into SQL.
196  
-                if where:
197  
-                    sql.append(where and "WHERE " + " AND ".join(where))
198  
-
199  
-                # ORDER BY clause
200  
-                order_by = []
201  
-                if self._order_by is not None:
202  
-                    ordering_to_use = self._order_by
203  
-                else:
204  
-                    ordering_to_use = opts.ordering
205  
-                for f in handle_legacy_orderlist(ordering_to_use):
206  
-                    if f == '?': # Special case.
207  
-                        order_by.append(DatabaseOperations().random_function_sql())
208  
-                    else:
209  
-                        if f.startswith('-'):
210  
-                            col_name = f[1:]
211  
-                            order = "DESC"
212  
-                        else:
213  
-                            col_name = f
214  
-                            order = "ASC"
215  
-                        if "." in col_name:
216  
-                            table_prefix, col_name = col_name.split('.', 1)
217  
-                            table_prefix = qn(table_prefix) + '.'
218  
-                        else:
219  
-                            # Use the database table as a column prefix if it wasn't given,
220  
-                            # and if the requested column isn't a custom SELECT.
221  
-                            if "." not in col_name and col_name not in (self._select or ()):
222  
-                                table_prefix = qn(opts.db_table) + '.'
223  
-                            else:
224  
-                                table_prefix = ''
225  
-                        order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order))
226  
-                if order_by:
227  
-                    sql.append("ORDER BY " + ", ".join(order_by))
228  
-
229  
-                # Look for column name collisions in the select elements
230  
-                # and fix them with an AS alias.  This allows us to do a
231  
-                # SELECT * later in the paging query.
232  
-                cols = [clause.split('.')[-1] for clause in select]
233  
-                for index, col in enumerate(cols):
234  
-                    if cols.count(col) > 1:
235  
-                        col = '%s%d' % (col.replace('"', ''), index)
236  
-                        cols[index] = col
237  
-                        select[index] = '%s AS %s' % (select[index], col)
238  
-
239  
-                # LIMIT and OFFSET clauses
240  
-                # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
241  
-                select_clause = ",".join(select)
242  
-                distinct = (self._distinct and "DISTINCT " or "")
243  
-
244  
-                if order_by:
245  
-                    order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by))
246  
-                else:
247  
-                    #Oracle's row_number() function always requires an order-by clause.
248  
-                    #So we need to define a default order-by, since none was provided.
249  
-                    order_by_clause = " OVER (ORDER BY %s.%s)" % \
250  
-                        (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column))
251  
-                # limit_and_offset_clause
252  
-                if self._limit is None:
253  
-                    assert self._offset is None, "'offset' is not allowed without 'limit'"
254  
-
255  
-                if self._offset is not None:
256  
-                    offset = int(self._offset)
257  
-                else:
258  
-                    offset = 0
259  
-                if self._limit is not None:
260  
-                    limit = int(self._limit)
261  
-                else:
262  
-                    limit = None
263  
-
264  
-                limit_and_offset_clause = ''
265  
-                if limit is not None:
266  
-                    limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
267  
-                elif offset:
268  
-                    limit_and_offset_clause = "WHERE rn > %s" % (offset)
269  
-
270  
-                if len(limit_and_offset_clause) > 0:
271  
-                    fmt = \
272  
-    """SELECT * FROM
273  
-      (SELECT %s%s,
274  
-              ROW_NUMBER()%s AS rn
275  
-       %s)
276  
-    %s"""
277  
-                    full_query = fmt % (distinct, select_clause,
278  
-                                        order_by_clause, ' '.join(sql).strip(),
279  
-                                        limit_and_offset_clause)
280  
-                else:
281  
-                    full_query = None
282  
-
283  
-                if get_full_query:
284  
-                    return select, " ".join(sql), params, full_query
285  
-                else:
286  
-                    return select, " ".join(sql), params
  103
+        # Getting the base default `Query` object.
  104
+        DefaultQuery = DefaultQuerySet().query.__class__
287 105
 
  106
+        class OracleQuery(DefaultQuery):
288 107
             def resolve_columns(self, row, fields=()):
289 108
                 from django.db.models.fields import DateField, DateTimeField, \
290  
-                    TimeField, BooleanField, NullBooleanField, DecimalField, Field
  109
+                     TimeField, BooleanField, NullBooleanField, DecimalField, Field
291 110
                 values = []
292 111
                 for value, field in map(None, row, fields):
293 112
                     if isinstance(value, Database.LOB):
@@ -331,6 +150,66 @@ def resolve_columns(self, row, fields=()):
331 150
                     values.append(value)
332 151
                 return values
333 152
 
  153
+            def as_sql(self, with_limits=True):
  154
+                """
  155
+                Creates the SQL for this query. Returns the SQL string and list
  156
+                of parameters.  This is overriden from the original Query class
  157
+                to accommodate Oracle's limit/offset SQL.
  158
+
  159
+                If 'with_limits' is False, any limit/offset information is not
  160
+                included in the query.
  161
+                """
  162
+                # The `do_offset` flag indicates whether we need to construct
  163
+                # the SQL needed to use limit/offset w/Oracle.
  164
+                do_offset = with_limits and (self.high_mark or self.low_mark)
  165
+
  166
+                # If no offsets, just return the result of the base class
  167
+                # `as_sql`.
  168
+                if not do_offset:
  169
+                    return super(OracleQuery, self).as_sql(with_limits=False)
  170
+
  171
+                # `get_columns` needs to be called before `get_ordering` to
  172
+                # populate `_select_alias`.
  173
+                self.pre_sql_setup()
  174
+                out_cols = self.get_columns()
  175
+                ordering = self.get_ordering()
  176
+
  177
+                # Getting the "ORDER BY" SQL for the ROW_NUMBER() result.
  178
+                if ordering:
  179
+                    rn_orderby = ', '.join(ordering)
  180
+                else:
  181
+                    # Oracle's ROW_NUMBER() function always requires an
  182
+                    # order-by clause.  So we need to define a default
  183
+                    # order-by, since none was provided.
  184
+                    qn = self.quote_name_unless_alias
  185
+                    opts = self.model._meta
  186
+                    rn_orderby = '%s.%s' % (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column))
  187
+
  188
+                # Getting the selection SQL and the params, which has the `rn`
  189
+                # extra selection SQL; we pop `rn` after this completes so we do
  190
+                # not get the attribute on the returned models.
  191
+                self.extra_select['rn'] = 'ROW_NUMBER() OVER (ORDER BY %s )' % rn_orderby
  192
+                sql, params= super(OracleQuery, self).as_sql(with_limits=False)
  193
+                self.extra_select.pop('rn')
  194
+
  195
+                # Constructing the result SQL, using the initial select SQL
  196
+                # obtained above.
  197
+                result = ['SELECT * FROM (%s)' % sql]
  198
+
  199
+                # Place WHERE condition on `rn` for the desired range.
  200
+                result.append('WHERE rn > %d' % self.low_mark)
  201
+                if self.high_mark:
  202
+                    result.append('AND rn <= %d' % self.high_mark)
  203
+
  204
+                # Returning the SQL w/params.
  205
+                return ' '.join(result), params
  206
+
  207
+        from django.db import connection
  208
+        class OracleQuerySet(DefaultQuerySet):
  209
+            "The OracleQuerySet is overriden to use OracleQuery."
  210
+            def __init__(self, model=None, query=None):
  211
+                super(OracleQuerySet, self).__init__(model=model, query=query)
  212
+                self.query = query or OracleQuery(self.model, connection)
334 213
         return OracleQuerySet
335 214
 
336 215
     def quote_name(self, name):

0 notes on commit 22bb040

Please sign in to comment.
Something went wrong with that request. Please try again.