register LDAP compiler
[matthijs/upstream/django-ldapdb.git] / ldapdb / backends / ldap / compiler.py
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
35 from django.db.models.sql import compiler
36
37 import ldap
38
39 class SQLCompiler(object):
40     def __init__(self, query, connection, using):
41         self.query = query
42         self.connection = connection
43         self.using = using
44
45     def results_iter(self):
46         if self.query.select_fields:
47             fields = self.query.select_fields
48         else:
49             fields = self.query.model._meta.fields
50
51         attrlist = [ x.db_column for x in fields if x.db_column ]
52
53         try:
54             vals = self.connection.search_s(
55                 self.query.model.base_dn,
56                 self.query.model.search_scope,
57                 filterstr=self.query._ldap_filter(),
58                 attrlist=attrlist,
59             )
60         except ldap.NO_SUCH_OBJECT:
61             return
62
63         # perform sorting
64         if self.query.extra_order_by:
65             ordering = self.query.extra_order_by
66         elif not self.query.default_ordering:
67             ordering = self.query.order_by
68         else:
69             ordering = self.query.order_by or self.query.model._meta.ordering
70         def cmpvals(x, y):
71             for fieldname in ordering:
72                 if fieldname.startswith('-'):
73                     fieldname = fieldname[1:]
74                     negate = True
75                 else:
76                     negate = False
77                 field = self.query.model._meta.get_field(fieldname)
78                 attr_x = field.from_ldap(x[1].get(field.db_column, []), connection=self.connection)
79                 attr_y = field.from_ldap(y[1].get(field.db_column, []), connection=self.connection)
80                 # perform case insensitive comparison
81                 if hasattr(attr_x, 'lower'):
82                     attr_x = attr_x.lower()
83                 if hasattr(attr_y, 'lower'):
84                     attr_y = attr_y.lower()
85                 val = negate and cmp(attr_y, attr_x) or cmp(attr_x, attr_y)
86                 if val:
87                     return val
88             return 0
89         vals = sorted(vals, cmp=cmpvals)
90
91         # process results
92         pos = 0
93         for dn, attrs in vals:
94             # FIXME : This is not optimal, we retrieve more results than we need
95             # but there is probably no other options as we can't perform ordering
96             # server side.
97             if (self.query.low_mark and pos < self.query.low_mark) or \
98                (self.query.high_mark is not None and pos >= self.query.high_mark):
99                 pos += 1
100                 continue
101             row = []
102             for field in iter(fields):
103                 if field.attname == 'dn':
104                     row.append(dn)
105                 elif hasattr(field, 'from_ldap'):
106                     row.append(field.from_ldap(attrs.get(field.db_column, []), connection=self.connection))
107                 else:
108                     row.append(None)
109             yield row
110             pos += 1
111
112 class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
113     pass
114
115 class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
116     pass
117
118 class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
119     pass
120
121 class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler):
122     pass
123
124 class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler):
125     pass
126