Add Django-1.2.1
[frozenviper.git] / Django-1.2.1 / build / lib.linux-i686-2.6 / django / contrib / gis / db / models / sql / compiler.py
blob78eeeafe190ac114c148ebf32803a28d856264f8
1 from itertools import izip
2 from django.db.backends.util import truncate_name
3 from django.db.models.sql import compiler
4 from django.db.models.sql.constants import TABLE_NAME
5 from django.db.models.sql.query import get_proxied_model
7 SQLCompiler = compiler.SQLCompiler
9 class GeoSQLCompiler(compiler.SQLCompiler):
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.
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.
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))
60 if hasattr(col, 'alias'):
61 aliases.add(col.alias)
62 col_aliases.add(col.alias)
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)
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 ''
78 for alias, aggregate in self.query.aggregate_select.items()
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)
94 self._select_aliases = aliases
95 return result
97 def get_default_columns(self, with_aliases=False, col_aliases=None,
98 start_alias=None, opts=None, as_pairs=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.
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).
110 This routine is overridden from Query to handle customized selection of
111 geometry columns.
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)
121 if start_alias:
122 seen = {None: start_alias}
123 for field, model in opts.get_fields_with_model():
124 if start_alias:
125 try:
126 alias = seen[model]
127 except KeyError:
128 if model is proxied_model:
129 alias = start_alias
130 else:
131 link_field = opts.get_ancestor_link(model)
132 alias = self.query.join((start_alias, model._meta.db_table,
133 link_field.column, model._meta.pk.column))
134 seen[model] = alias
135 else:
136 # If we're starting from the base model of the queryset, the
137 # aliases will have already been set up in pre_sql_setup(), so
138 # we can save time here.
139 alias = self.query.included_inherited_models[model]
140 table = self.query.alias_map[alias][TABLE_NAME]
141 if table in only_load and field.column not in only_load[table]:
142 continue
143 if as_pairs:
144 result.append((alias, field.column))
145 aliases.add(alias)
146 continue
147 # This part of the function is customized for GeoQuery. We
148 # see if there was any custom selection specified in the
149 # dictionary, and set up the selection format appropriately.
150 field_sel = self.get_field_select(field, alias)
151 if with_aliases and field.column in col_aliases:
152 c_alias = 'Col%d' % len(col_aliases)
153 result.append('%s AS %s' % (field_sel, c_alias))
154 col_aliases.add(c_alias)
155 aliases.add(c_alias)
156 else:
157 r = field_sel
158 result.append(r)
159 aliases.add(r)
160 if with_aliases:
161 col_aliases.add(field.column)
162 return result, aliases
164 def resolve_columns(self, row, fields=()):
166 This routine is necessary so that distances and geometries returned
167 from extra selection SQL get resolved appropriately into Python
168 objects.
170 values = []
171 aliases = self.query.extra_select.keys()
172 if self.query.aggregates:
173 # If we have an aggregate annotation, must extend the aliases
174 # so their corresponding row values are included.
175 aliases.extend([None for i in xrange(len(self.query.aggregates))])
177 # Have to set a starting row number offset that is used for
178 # determining the correct starting row index -- needed for
179 # doing pagination with Oracle.
180 rn_offset = 0
181 if self.connection.ops.oracle:
182 if self.query.high_mark is not None or self.query.low_mark: rn_offset = 1
183 index_start = rn_offset + len(aliases)
185 # Converting any extra selection values (e.g., geometries and
186 # distance objects added by GeoQuerySet methods).
187 values = [self.query.convert_values(v,
188 self.query.extra_select_fields.get(a, None),
189 self.connection)
190 for v, a in izip(row[rn_offset:index_start], aliases)]
191 if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
192 # We resolve the rest of the columns if we're on Oracle or if
193 # the `geo_values` attribute is defined.
194 for value, field in map(None, row[index_start:], fields):
195 values.append(self.query.convert_values(value, field, connection=self.connection))
196 else:
197 values.extend(row[index_start:])
198 return tuple(values)
200 #### Routines unique to GeoQuery ####
201 def get_extra_select_format(self, alias):
202 sel_fmt = '%s'
203 if alias in self.query.custom_select:
204 sel_fmt = sel_fmt % self.query.custom_select[alias]
205 return sel_fmt
207 def get_field_select(self, field, alias=None, column=None):
209 Returns the SELECT SQL string for the given field. Figures out
210 if any custom selection SQL is needed for the column The `alias`
211 keyword may be used to manually specify the database table where
212 the column exists, if not in the model associated with this
213 `GeoQuery`. Similarly, `column` may be used to specify the exact
214 column name, rather than using the `column` attribute on `field`.
216 sel_fmt = self.get_select_format(field)
217 if field in self.query.custom_select:
218 field_sel = sel_fmt % self.query.custom_select[field]
219 else:
220 field_sel = sel_fmt % self._field_column(field, alias, column)
221 return field_sel
223 def get_select_format(self, fld):
225 Returns the selection format string, depending on the requirements
226 of the spatial backend. For example, Oracle and MySQL require custom
227 selection formats in order to retrieve geometries in OGC WKT. For all
228 other fields a simple '%s' format string is returned.
230 if self.connection.ops.select and hasattr(fld, 'geom_type'):
231 # This allows operations to be done on fields in the SELECT,
232 # overriding their values -- used by the Oracle and MySQL
233 # spatial backends to get database values as WKT, and by the
234 # `transform` method.
235 sel_fmt = self.connection.ops.select
237 # Because WKT doesn't contain spatial reference information,
238 # the SRID is prefixed to the returned WKT to ensure that the
239 # transformed geometries have an SRID different than that of the
240 # field -- this is only used by `transform` for Oracle and
241 # SpatiaLite backends.
242 if self.query.transformed_srid and ( self.connection.ops.oracle or
243 self.connection.ops.spatialite ):
244 sel_fmt = "'SRID=%d;'||%s" % (self.query.transformed_srid, sel_fmt)
245 else:
246 sel_fmt = '%s'
247 return sel_fmt
249 # Private API utilities, subject to change.
250 def _field_column(self, field, table_alias=None, column=None):
252 Helper function that returns the database column for the given field.
253 The table and column are returned (quoted) in the proper format, e.g.,
254 `"geoapp_city"."point"`. If `table_alias` is not specified, the
255 database table associated with the model of this `GeoQuery` will be
256 used. If `column` is specified, it will be used instead of the value
257 in `field.column`.
259 if table_alias is None: table_alias = self.query.model._meta.db_table
260 return "%s.%s" % (self.quote_name_unless_alias(table_alias),
261 self.connection.ops.quote_name(column or field.column))
263 class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
264 pass
266 class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
267 pass
269 class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler):
270 pass
272 class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
273 pass
275 class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
276 pass