compiler.py.svn-base 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # -*- coding: utf-8 -*-
  2. #
  3. # django-ldapdb
  4. # Copyright (c) 2009-2010, Bolloré telecom
  5. # All rights reserved.
  6. #
  7. # See AUTHORS file for a full list of contributors.
  8. #
  9. # Redistribution and use in source and binary forms, with or without modification,
  10. # are permitted provided that the following conditions are met:
  11. #
  12. # 1. Redistributions of source code must retain the above copyright notice,
  13. # this list of conditions and the following disclaimer.
  14. #
  15. # 2. Redistributions in binary form must reproduce the above copyright
  16. # notice, this list of conditions and the following disclaimer in the
  17. # documentation and/or other materials provided with the distribution.
  18. #
  19. # 3. Neither the name of Bolloré telecom nor the names of its contributors
  20. # may be used to endorse or promote products derived from this software
  21. # without specific prior written permission.
  22. #
  23. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  24. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  25. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  26. # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  27. # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  28. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  29. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  30. # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  32. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33. #
  34. import ldap
  35. from django.db.models.sql import aggregates, compiler
  36. from django.db.models.sql.where import AND, OR
  37. def get_lookup_operator(lookup_type):
  38. if lookup_type == 'gte':
  39. return '>='
  40. elif lookup_type == 'lte':
  41. return '<='
  42. else:
  43. return '='
  44. def query_as_ldap(query):
  45. filterstr = ''.join(['(objectClass=%s)' % cls for cls in query.model.object_classes])
  46. sql, params = where_as_ldap(query.where)
  47. filterstr += sql
  48. return '(&%s)' % filterstr
  49. def where_as_ldap(self):
  50. bits = []
  51. for item in self.children:
  52. if hasattr(item, 'as_sql'):
  53. sql, params = where_as_ldap(item)
  54. bits.append(sql)
  55. continue
  56. constraint, lookup_type, y, values = item
  57. comp = get_lookup_operator(lookup_type)
  58. if lookup_type == 'in':
  59. equal_bits = [ "(%s%s%s)" % (constraint.col, comp, value) for value in values ]
  60. clause = '(|%s)' % ''.join(equal_bits)
  61. else:
  62. clause = "(%s%s%s)" % (constraint.col, comp, values)
  63. bits.append(clause)
  64. if not len(bits):
  65. return '', []
  66. if len(bits) == 1:
  67. sql_string = bits[0]
  68. elif self.connector == AND:
  69. sql_string = '(&%s)' % ''.join(bits)
  70. elif self.connector == OR:
  71. sql_string = '(|%s)' % ''.join(bits)
  72. else:
  73. raise Exception("Unhandled WHERE connector: %s" % self.connector)
  74. if self.negated:
  75. sql_string = ('(!%s)' % sql_string)
  76. return sql_string, []
  77. class SQLCompiler(object):
  78. def __init__(self, query, connection, using):
  79. self.query = query
  80. self.connection = connection
  81. self.using = using
  82. def execute_sql(self, result_type=compiler.MULTI):
  83. if result_type !=compiler.SINGLE:
  84. raise Exception("LDAP does not support MULTI queries")
  85. for key, aggregate in self.query.aggregate_select.items():
  86. if not isinstance(aggregate, aggregates.Count):
  87. raise Exception("Unsupported aggregate %s" % aggregate)
  88. try:
  89. vals = self.connection.search_s(
  90. self.query.model.base_dn,
  91. self.query.model.search_scope,
  92. filterstr=query_as_ldap(self.query),
  93. attrlist=['dn'],
  94. )
  95. except ldap.NO_SUCH_OBJECT:
  96. vals = []
  97. if not vals:
  98. return None
  99. output = []
  100. for alias, col in self.query.extra_select.iteritems():
  101. output.append(col[0])
  102. for key, aggregate in self.query.aggregate_select.items():
  103. if isinstance(aggregate, aggregates.Count):
  104. output.append(len(vals))
  105. else:
  106. output.append(None)
  107. return output
  108. def results_iter(self):
  109. if self.query.select_fields:
  110. fields = self.query.select_fields
  111. else:
  112. fields = self.query.model._meta.fields
  113. attrlist = [ x.db_column for x in fields if x.db_column ]
  114. try:
  115. vals = self.connection.search_s(
  116. self.query.model.base_dn,
  117. self.query.model.search_scope,
  118. filterstr=query_as_ldap(self.query),
  119. attrlist=attrlist,
  120. )
  121. except ldap.NO_SUCH_OBJECT:
  122. return
  123. # perform sorting
  124. if self.query.extra_order_by:
  125. ordering = self.query.extra_order_by
  126. elif not self.query.default_ordering:
  127. ordering = self.query.order_by
  128. else:
  129. ordering = self.query.order_by or self.query.model._meta.ordering
  130. def cmpvals(x, y):
  131. for fieldname in ordering:
  132. if fieldname.startswith('-'):
  133. fieldname = fieldname[1:]
  134. negate = True
  135. else:
  136. negate = False
  137. if fieldname == 'pk':
  138. fieldname = self.query.model._meta.pk.name
  139. field = self.query.model._meta.get_field(fieldname)
  140. attr_x = field.from_ldap(x[1].get(field.db_column, []), connection=self.connection)
  141. attr_y = field.from_ldap(y[1].get(field.db_column, []), connection=self.connection)
  142. # perform case insensitive comparison
  143. if hasattr(attr_x, 'lower'):
  144. attr_x = attr_x.lower()
  145. if hasattr(attr_y, 'lower'):
  146. attr_y = attr_y.lower()
  147. val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y)
  148. if val:
  149. return val
  150. return 0
  151. vals = sorted(vals, cmp=cmpvals)
  152. # process results
  153. pos = 0
  154. for dn, attrs in vals:
  155. # FIXME : This is not optimal, we retrieve more results than we need
  156. # but there is probably no other options as we can't perform ordering
  157. # server side.
  158. if (self.query.low_mark and pos < self.query.low_mark) or \
  159. (self.query.high_mark is not None and pos >= self.query.high_mark):
  160. pos += 1
  161. continue
  162. row = []
  163. for field in iter(fields):
  164. if field.attname == 'dn':
  165. row.append(dn)
  166. elif hasattr(field, 'from_ldap'):
  167. row.append(field.from_ldap(attrs.get(field.db_column, []), connection=self.connection))
  168. else:
  169. row.append(None)
  170. yield row
  171. pos += 1
  172. class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
  173. pass
  174. class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
  175. def execute_sql(self, result_type=compiler.MULTI):
  176. try:
  177. vals = self.connection.search_s(
  178. self.query.model.base_dn,
  179. self.query.model.search_scope,
  180. filterstr=query_as_ldap(self.query),
  181. attrlist=['dn'],
  182. )
  183. except ldap.NO_SUCH_OBJECT:
  184. return
  185. # FIXME : there is probably a more efficient way to do this
  186. for dn, attrs in vals:
  187. self.connection.delete_s(dn)
  188. class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
  189. pass
  190. class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
  191. pass
  192. class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):
  193. pass