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):
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
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
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())
30 col_aliases
= aliases
.copy()
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)):
39 table
= self
.query
.alias_map
[alias
][TABLE_NAME
]
40 if table
in only_load
and col
not in only_load
[table
]:
42 r
= self
.get_field_select(field
, alias
, column
)
44 if col
[1] in col_aliases
:
45 c_alias
= 'Col%d' % len(col_aliases
)
46 result
.append('%s AS %s' % (r
, c_alias
))
48 col_aliases
.add(c_alias
)
50 result
.append('%s AS %s' % (r
, qn2(col
[1])))
52 col_aliases
.add(col
[1])
56 col_aliases
.add(col
[1])
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
,
68 aliases
.update(new_aliases
)
70 max_name_length
= self
.connection
.ops
.max_name_length()
73 self
.get_extra_select_format(alias
) % aggregate
.as_sql(qn
, self
.connection
),
75 and ' AS %s' % qn(truncate_name(alias
, max_name_length
))
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
))
88 col_aliases
.add(c_alias
)
94 self
._select
_aliases
= aliases
97 def get_default_columns(self
, with_aliases
=False, col_aliases
=None,
98 start_alias
=None, opts
=None, as_pairs
=False):
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
115 opts
= self
.query
.model
._meta
117 only_load
= self
.deferred_to_columns()
118 # Skip all proxy to the root proxied model
119 proxied_model
= get_proxied_model(opts
)
122 seen
= {None: start_alias
}
123 for field
, model
in opts
.get_fields_with_model():
128 if model
is proxied_model
:
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
))
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
]:
144 result
.append((alias
, field
.column
))
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
)
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
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.
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),
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
))
197 values
.extend(row
[index_start
:])
200 #### Routines unique to GeoQuery ####
201 def get_extra_select_format(self
, alias
):
203 if alias
in self
.query
.custom_select
:
204 sel_fmt
= sel_fmt
% self
.query
.custom_select
[alias
]
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
]
220 field_sel
= sel_fmt
% self
._field
_column
(field
, alias
, column
)
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
)
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
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
):
266 class SQLDeleteCompiler(compiler
.SQLDeleteCompiler
, GeoSQLCompiler
):
269 class SQLUpdateCompiler(compiler
.SQLUpdateCompiler
, GeoSQLCompiler
):
272 class SQLAggregateCompiler(compiler
.SQLAggregateCompiler
, GeoSQLCompiler
):
275 class SQLDateCompiler(compiler
.SQLDateCompiler
, GeoSQLCompiler
):