PageRenderTime 105ms CodeModel.GetById 40ms app.highlight 27ms RepoModel.GetById 34ms app.codeStats 0ms

/django/contrib/gis/db/models/sql/compiler.py

https://code.google.com/p/mango-py/
Python | 278 lines | 223 code | 10 blank | 45 comment | 30 complexity | 801b0e58caeff4b52f25e32616cbca2c MD5 | raw file
  1from itertools import izip
  2from django.db.backends.util import truncate_name
  3from django.db.models.sql import compiler
  4from django.db.models.sql.constants import TABLE_NAME
  5from django.db.models.sql.query import get_proxied_model
  6
  7SQLCompiler = compiler.SQLCompiler
  8
  9class GeoSQLCompiler(compiler.SQLCompiler):
 10
 11    def get_columns(self, with_aliases=False):
 12        """
 13        Return the list of columns to use in the select statement. If no
 14        columns have been specified, returns all columns relating to fields in
 15        the model.
 16
 17        If 'with_aliases' is true, any column names that are duplicated
 18        (without the table names) are given unique aliases. This is needed in
 19        some cases to avoid ambiguitity with nested queries.
 20
 21        This routine is overridden from Query to handle customized selection of
 22        geometry columns.
 23        """
 24        qn = self.quote_name_unless_alias
 25        qn2 = self.connection.ops.quote_name
 26        result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
 27                  for alias, col in self.query.extra_select.iteritems()]
 28        aliases = set(self.query.extra_select.keys())
 29        if with_aliases:
 30            col_aliases = aliases.copy()
 31        else:
 32            col_aliases = set()
 33        if self.query.select:
 34            only_load = self.deferred_to_columns()
 35            # This loop customized for GeoQuery.
 36            for col, field in izip(self.query.select, self.query.select_fields):
 37                if isinstance(col, (list, tuple)):
 38                    alias, column = col
 39                    table = self.query.alias_map[alias][TABLE_NAME]
 40                    if table in only_load and col not in only_load[table]:
 41                        continue
 42                    r = self.get_field_select(field, alias, column)
 43                    if with_aliases:
 44                        if col[1] in col_aliases:
 45                            c_alias = 'Col%d' % len(col_aliases)
 46                            result.append('%s AS %s' % (r, c_alias))
 47                            aliases.add(c_alias)
 48                            col_aliases.add(c_alias)
 49                        else:
 50                            result.append('%s AS %s' % (r, qn2(col[1])))
 51                            aliases.add(r)
 52                            col_aliases.add(col[1])
 53                    else:
 54                        result.append(r)
 55                        aliases.add(r)
 56                        col_aliases.add(col[1])
 57                else:
 58                    result.append(col.as_sql(qn, self.connection))
 59
 60                    if hasattr(col, 'alias'):
 61                        aliases.add(col.alias)
 62                        col_aliases.add(col.alias)
 63
 64        elif self.query.default_cols:
 65            cols, new_aliases = self.get_default_columns(with_aliases,
 66                    col_aliases)
 67            result.extend(cols)
 68            aliases.update(new_aliases)
 69
 70        max_name_length = self.connection.ops.max_name_length()
 71        result.extend([
 72                '%s%s' % (
 73                    self.get_extra_select_format(alias) % aggregate.as_sql(qn, self.connection),
 74                    alias is not None
 75                        and ' AS %s' % qn(truncate_name(alias, max_name_length))
 76                        or ''
 77                    )
 78                for alias, aggregate in self.query.aggregate_select.items()
 79        ])
 80
 81        # This loop customized for GeoQuery.
 82        for (table, col), field in izip(self.query.related_select_cols, self.query.related_select_fields):
 83            r = self.get_field_select(field, table, col)
 84            if with_aliases and col in col_aliases:
 85                c_alias = 'Col%d' % len(col_aliases)
 86                result.append('%s AS %s' % (r, c_alias))
 87                aliases.add(c_alias)
 88                col_aliases.add(c_alias)
 89            else:
 90                result.append(r)
 91                aliases.add(r)
 92                col_aliases.add(col)
 93
 94        self._select_aliases = aliases
 95        return result
 96
 97    def get_default_columns(self, with_aliases=False, col_aliases=None,
 98            start_alias=None, opts=None, as_pairs=False, local_only=False):
 99        """
100        Computes the default columns for selecting every field in the base
101        model. Will sometimes be called to pull in related models (e.g. via
102        select_related), in which case "opts" and "start_alias" will be given
103        to provide a starting point for the traversal.
104
105        Returns a list of strings, quoted appropriately for use in SQL
106        directly, as well as a set of aliases used in the select statement (if
107        'as_pairs' is True, returns a list of (alias, col_name) pairs instead
108        of strings as the first component and None as the second component).
109
110        This routine is overridden from Query to handle customized selection of
111        geometry columns.
112        """
113        result = []
114        if opts is None:
115            opts = self.query.model._meta
116        aliases = set()
117        only_load = self.deferred_to_columns()
118        # Skip all proxy to the root proxied model
119        proxied_model = get_proxied_model(opts)
120
121        if start_alias:
122            seen = {None: start_alias}
123        for field, model in opts.get_fields_with_model():
124            if local_only and model is not None:
125                continue
126            if start_alias:
127                try:
128                    alias = seen[model]
129                except KeyError:
130                    if model is proxied_model:
131                        alias = start_alias
132                    else:
133                        link_field = opts.get_ancestor_link(model)
134                        alias = self.query.join((start_alias, model._meta.db_table,
135                                link_field.column, model._meta.pk.column))
136                    seen[model] = alias
137            else:
138                # If we're starting from the base model of the queryset, the
139                # aliases will have already been set up in pre_sql_setup(), so
140                # we can save time here.
141                alias = self.query.included_inherited_models[model]
142            table = self.query.alias_map[alias][TABLE_NAME]
143            if table in only_load and field.column not in only_load[table]:
144                continue
145            if as_pairs:
146                result.append((alias, field.column))
147                aliases.add(alias)
148                continue
149            # This part of the function is customized for GeoQuery. We
150            # see if there was any custom selection specified in the
151            # dictionary, and set up the selection format appropriately.
152            field_sel = self.get_field_select(field, alias)
153            if with_aliases and field.column in col_aliases:
154                c_alias = 'Col%d' % len(col_aliases)
155                result.append('%s AS %s' % (field_sel, c_alias))
156                col_aliases.add(c_alias)
157                aliases.add(c_alias)
158            else:
159                r = field_sel
160                result.append(r)
161                aliases.add(r)
162                if with_aliases:
163                    col_aliases.add(field.column)
164        return result, aliases
165
166    def resolve_columns(self, row, fields=()):
167        """
168        This routine is necessary so that distances and geometries returned
169        from extra selection SQL get resolved appropriately into Python
170        objects.
171        """
172        values = []
173        aliases = self.query.extra_select.keys()
174        if self.query.aggregates:
175            # If we have an aggregate annotation, must extend the aliases
176            # so their corresponding row values are included.
177            aliases.extend([None for i in xrange(len(self.query.aggregates))])
178
179        # Have to set a starting row number offset that is used for
180        # determining the correct starting row index -- needed for
181        # doing pagination with Oracle.
182        rn_offset = 0
183        if self.connection.ops.oracle:
184            if self.query.high_mark is not None or self.query.low_mark: rn_offset = 1
185        index_start = rn_offset + len(aliases)
186
187        # Converting any extra selection values (e.g., geometries and
188        # distance objects added by GeoQuerySet methods).
189        values = [self.query.convert_values(v,
190                               self.query.extra_select_fields.get(a, None),
191                               self.connection)
192                  for v, a in izip(row[rn_offset:index_start], aliases)]
193        if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
194            # We resolve the rest of the columns if we're on Oracle or if
195            # the `geo_values` attribute is defined.
196            for value, field in map(None, row[index_start:], fields):
197                values.append(self.query.convert_values(value, field, connection=self.connection))
198        else:
199            values.extend(row[index_start:])
200        return tuple(values)
201
202    #### Routines unique to GeoQuery ####
203    def get_extra_select_format(self, alias):
204        sel_fmt = '%s'
205        if hasattr(self.query, 'custom_select') and alias in self.query.custom_select:
206            sel_fmt = sel_fmt % self.query.custom_select[alias]
207        return sel_fmt
208
209    def get_field_select(self, field, alias=None, column=None):
210        """
211        Returns the SELECT SQL string for the given field.  Figures out
212        if any custom selection SQL is needed for the column  The `alias`
213        keyword may be used to manually specify the database table where
214        the column exists, if not in the model associated with this
215        `GeoQuery`.  Similarly, `column` may be used to specify the exact
216        column name, rather than using the `column` attribute on `field`.
217        """
218        sel_fmt = self.get_select_format(field)
219        if field in self.query.custom_select:
220            field_sel = sel_fmt % self.query.custom_select[field]
221        else:
222            field_sel = sel_fmt % self._field_column(field, alias, column)
223        return field_sel
224
225    def get_select_format(self, fld):
226        """
227        Returns the selection format string, depending on the requirements
228        of the spatial backend.  For example, Oracle and MySQL require custom
229        selection formats in order to retrieve geometries in OGC WKT. For all
230        other fields a simple '%s' format string is returned.
231        """
232        if self.connection.ops.select and hasattr(fld, 'geom_type'):
233            # This allows operations to be done on fields in the SELECT,
234            # overriding their values -- used by the Oracle and MySQL
235            # spatial backends to get database values as WKT, and by the
236            # `transform` method.
237            sel_fmt = self.connection.ops.select
238
239            # Because WKT doesn't contain spatial reference information,
240            # the SRID is prefixed to the returned WKT to ensure that the
241            # transformed geometries have an SRID different than that of the
242            # field -- this is only used by `transform` for Oracle and
243            # SpatiaLite backends.
244            if self.query.transformed_srid and ( self.connection.ops.oracle or
245                                                 self.connection.ops.spatialite ):
246                sel_fmt = "'SRID=%d;'||%s" % (self.query.transformed_srid, sel_fmt)
247        else:
248            sel_fmt = '%s'
249        return sel_fmt
250
251    # Private API utilities, subject to change.
252    def _field_column(self, field, table_alias=None, column=None):
253        """
254        Helper function that returns the database column for the given field.
255        The table and column are returned (quoted) in the proper format, e.g.,
256        `"geoapp_city"."point"`.  If `table_alias` is not specified, the
257        database table associated with the model of this `GeoQuery` will be
258        used.  If `column` is specified, it will be used instead of the value
259        in `field.column`.
260        """
261        if table_alias is None: table_alias = self.query.model._meta.db_table
262        return "%s.%s" % (self.quote_name_unless_alias(table_alias),
263                          self.connection.ops.quote_name(column or field.column))
264
265class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
266    pass
267
268class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
269    pass
270
271class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler):
272    pass
273
274class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
275    pass
276
277class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
278    pass