PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

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