1 # -*- coding: utf-8 -*-
4 # Copyright (c) 2009-2010, Bolloré telecom
7 # See AUTHORS file for a full list of contributors.
9 # Redistribution and use in source and binary forms, with or without modification,
10 # are permitted provided that the following conditions are met:
12 # 1. Redistributions of source code must retain the above copyright notice,
13 # this list of conditions and the following disclaimer.
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.
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.
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.
37 from django.db.models.sql import aggregates, compiler
38 from django.db.models.sql.where import AND, OR
40 def get_lookup_operator(lookup_type):
41 if lookup_type == 'gte':
43 elif lookup_type == 'lte':
48 def query_as_ldap(query):
49 filterstr = ''.join(['(objectClass=%s)' % cls for cls in query.model.object_classes])
50 sql, params = where_as_ldap(query.where)
52 return '(&%s)' % filterstr
54 def where_as_ldap(self):
56 for item in self.children:
57 if hasattr(item, 'as_sql'):
58 sql, params = where_as_ldap(item)
62 constraint, lookup_type, y, values = item
63 comp = get_lookup_operator(lookup_type)
64 if lookup_type == 'in':
65 equal_bits = [ "(%s%s%s)" % (constraint.col, comp, value) for value in values ]
66 clause = '(|%s)' % ''.join(equal_bits)
68 clause = "(%s%s%s)" % (constraint.col, comp, values)
77 elif self.connector == AND:
78 sql_string = '(&%s)' % ''.join(bits)
79 elif self.connector == OR:
80 sql_string = '(|%s)' % ''.join(bits)
82 raise Exception("Unhandled WHERE connector: %s" % self.connector)
85 sql_string = ('(!%s)' % sql_string)
89 class SQLCompiler(object):
90 def __init__(self, query, connection, using):
92 self.connection = connection
95 def execute_sql(self, result_type=compiler.MULTI):
96 if result_type !=compiler.SINGLE:
97 raise Exception("LDAP does not support MULTI queries")
99 for key, aggregate in self.query.aggregate_select.items():
100 if not isinstance(aggregate, aggregates.Count):
101 raise Exception("Unsupported aggregate %s" % aggregate)
104 vals = self.connection.search_s(
105 self.query.model.base_dn,
106 self.query.model.search_scope,
107 filterstr=query_as_ldap(self.query),
110 except ldap.NO_SUCH_OBJECT:
117 for alias, col in self.query.extra_select.iteritems():
118 output.append(col[0])
119 for key, aggregate in self.query.aggregate_select.items():
120 if isinstance(aggregate, aggregates.Count):
121 output.append(len(vals))
126 def results_iter(self):
127 if self.query.select_fields:
128 fields = self.query.select_fields
130 fields = self.query.model._meta.fields
132 attrlist = [ x.db_column for x in fields if x.db_column ]
135 vals = self.connection.search_s(
136 self.query.model.base_dn,
137 self.query.model.search_scope,
138 filterstr=query_as_ldap(self.query),
141 except ldap.NO_SUCH_OBJECT:
145 if self.query.extra_order_by:
146 ordering = self.query.extra_order_by
147 elif not self.query.default_ordering:
148 ordering = self.query.order_by
150 ordering = self.query.order_by or self.query.model._meta.ordering
152 for fieldname in ordering:
153 if fieldname.startswith('-'):
154 fieldname = fieldname[1:]
158 field = self.query.model._meta.get_field(fieldname)
159 attr_x = field.from_ldap(x[1].get(field.db_column, []), connection=self.connection)
160 attr_y = field.from_ldap(y[1].get(field.db_column, []), connection=self.connection)
161 # perform case insensitive comparison
162 if hasattr(attr_x, 'lower'):
163 attr_x = attr_x.lower()
164 if hasattr(attr_y, 'lower'):
165 attr_y = attr_y.lower()
166 val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y)
170 vals = sorted(vals, cmp=cmpvals)
174 for dn, attrs in vals:
175 # FIXME : This is not optimal, we retrieve more results than we need
176 # but there is probably no other options as we can't perform ordering
178 if (self.query.low_mark and pos < self.query.low_mark) or \
179 (self.query.high_mark is not None and pos >= self.query.high_mark):
183 for field in iter(fields):
184 if field.attname == 'dn':
186 elif hasattr(field, 'from_ldap'):
187 row.append(field.from_ldap(attrs.get(field.db_column, []), connection=self.connection))
193 class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
196 class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
197 def execute_sql(self, result_type=compiler.MULTI):
199 vals = self.connection.search_s(
200 self.query.model.base_dn,
201 self.query.model.search_scope,
202 filterstr=query_as_ldap(self.query),
205 except ldap.NO_SUCH_OBJECT:
208 # FIXME : there is probably a more efficient way to do this
209 for dn, attrs in vals:
210 self.connection.delete_s(dn)
212 class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
215 class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
218 class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):