fix myplugin JS example on_new_record function
[myslice.git] / manifold / util / predicate.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Class Predicate: 
5 # Define a condition to join for example to Table instances.
6 # If this condition involves several fields, you may define a
7 # single Predicate using tuple of fields. 
8 #
9 # Copyright (C) UPMC Paris Universitas
10 # Authors:
11 #   Jordan AugĂ©       <jordan.auge@lip6.fr>
12 #   Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
13
14 from types                      import StringTypes
15 from manifold.util.type         import returns, accepts 
16
17 from operator import (
18     and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg
19 )
20
21 # Define the inclusion operators
22 class contains(type): pass
23 class included(type): pass
24
25 # New modifier: { contains 
26 class Predicate:
27
28     operators = {
29         '=='       : eq,
30         '!='       : ne,
31         '<'        : lt,
32         '<='       : le,
33         '>'        : gt,
34         '>='       : ge,
35         '&&'       : and_,
36         '||'       : or_,
37         'CONTAINS' : contains,
38         'INCLUDED' : included
39     }
40
41     operators_short = {
42         '=' : eq,
43         '~' : ne,
44         '<' : lt,
45         '[' : le,
46         '>' : gt,
47         ']' : ge,
48         '&' : and_,
49         '|' : or_,
50         '}' : contains,
51         '{' : included
52     }
53
54     def __init__(self, *args, **kwargs):
55         """
56         Build a Predicate instance.
57         Args: 
58             kwargs: You can pass:
59                 - 3 args (left, operator, right)
60                     left: The left operand (it may be a String instance or a tuple)
61                     operator: See Predicate.operators, this is the binary operator
62                         involved in this Predicate. 
63                     right: The right value (it may be a String instance
64                         or a literal (String, numerical value, tuple...))
65                 - 1 argument (list or tuple), containing three arguments
66                   (variable, operator, value)
67         """
68         if len(args) == 3:
69             key, op, value = args
70         elif len(args) == 1 and isinstance(args[0], (tuple,list)) and len(args[0]) == 3:
71             key, op, value = args[0]
72         elif len(args) == 1 and isinstance(args[0], Predicate):
73             key, op, value = args[0].get_tuple()
74         else:
75             raise Exception, "Bad initializer for Predicate (args = %r)" % args
76
77         assert not isinstance(value, (frozenset, dict, set)), "Invalid value type (type = %r)" % type(value)
78         if isinstance(value, list):
79             value = tuple(value)
80
81         self.key = key
82         if isinstance(op, StringTypes):
83             op = op.upper()
84         if op in self.operators.keys():
85             self.op = self.operators[op]
86         elif op in self.operators_short.keys():
87             self.op = self.operators_short[op]
88         else:
89             self.op = op
90
91         if isinstance(value, list):
92             self.value = tuple(value)
93         else:
94             self.value = value
95
96     @returns(StringTypes)
97     def __str__(self):
98         """
99         Returns:
100             The '%s' representation of this Predicate.
101         """
102         key, op, value = self.get_str_tuple()
103         if isinstance(value, (tuple, list, set, frozenset)):
104             value = [repr(v) for v in value]
105             value = "[%s]" % ", ".join(value)
106         return "%s %s %r" % (key, op, value) 
107
108     @returns(StringTypes)
109     def __repr__(self):
110         """
111         Returns:
112             The '%r' representation of this Predicate.
113         """
114         return "Predicate<%s %s %r>" % self.get_str_tuple()
115
116     def __hash__(self):
117         """
118         Returns:
119             The hash of this Predicate (this allows to define set of
120             Predicate instances).
121         """
122         return hash(self.get_tuple())
123
124     @returns(bool)
125     def __eq__(self, predicate):
126         """
127         Returns:
128             True iif self == predicate.
129         """
130         if not predicate:
131             return False
132         return self.get_tuple() == predicate.get_tuple()
133
134     def get_key(self):
135         """
136         Returns:
137             The left operand of this Predicate. It may be a String
138             or a tuple of Strings.
139         """
140         return self.key
141     
142     def set_key(self, key):
143         """
144         Set the left operand of this Predicate.
145         Params:
146             key: The new left operand.
147         """
148         self.key = key
149
150     def get_op(self):
151         return self.op
152
153     def get_value(self):
154         return self.value
155
156     def set_value(self, value):
157         self.value = value
158
159     def get_tuple(self):
160         return (self.key, self.op, self.value)
161
162     def get_str_op(self):
163         op_str = [s for s, op in self.operators.iteritems() if op == self.op]
164         return op_str[0]
165
166     def get_str_tuple(self):
167         return (self.key, self.get_str_op(), self.value,)
168
169     def to_list(self):
170         return list(self.get_str_tuple())
171
172     def match(self, dic, ignore_missing=False):
173         if isinstance(self.key, tuple):
174             print "PREDICATE MATCH", self.key
175             print dic
176             print "-----------------------------"
177         
178         # Can we match ?
179         if self.key not in dic:
180             return ignore_missing
181
182         if self.op == eq:
183             if isinstance(self.value, list):
184                 return (dic[self.key] in self.value) # array ?
185             else:
186                 return (dic[self.key] == self.value)
187         elif self.op == ne:
188             if isinstance(self.value, list):
189                 return (dic[self.key] not in self.value) # array ?
190             else:
191                 return (dic[self.key] != self.value) # array ?
192         elif self.op == lt:
193             if isinstance(self.value, StringTypes):
194                 # prefix match
195                 return dic[self.key].startswith('%s.' % self.value)
196             else:
197                 return (dic[self.key] < self.value)
198         elif self.op == le:
199             if isinstance(self.value, StringTypes):
200                 return dic[self.key] == self.value or dic[self.key].startswith('%s.' % self.value)
201             else:
202                 return (dic[self.key] <= self.value)
203         elif self.op == gt:
204             if isinstance(self.value, StringTypes):
205                 # prefix match
206                 return self.value.startswith('%s.' % dic[self.key])
207             else:
208                 return (dic[self.key] > self.value)
209         elif self.op == ge:
210             if isinstance(self.value, StringTypes):
211                 # prefix match
212                 return dic[self.key] == self.value or self.value.startswith('%s.' % dic[self.key])
213             else:
214                 return (dic[self.key] >= self.value)
215         elif self.op == and_:
216             return (dic[self.key] & self.value) # array ?
217         elif self.op == or_:
218             return (dic[self.key] | self.value) # array ?
219         elif self.op == contains:
220             method, subfield = self.key.split('.', 1)
221             return not not [ x for x in dic[method] if x[subfield] == self.value] 
222         elif self.op == included:
223             return dic[self.key] in self.value
224         else:
225             raise Exception, "Unexpected table format: %r" % dic
226
227     def filter(self, dic):
228         """
229         Filter dic according to the current predicate.
230         """
231
232         if '.' in self.key:
233             # users.hrn
234             method, subfield = self.key.split('.', 1)
235             if not method in dic:
236                 return None # XXX
237
238             if isinstance(dic[method], dict):
239                 # We have a 1..1 relationship: apply the same filter to the dict
240                 subpred = Predicate(subfield, self.op, self.value)
241                 match = subpred.match(dic[method])
242                 return dic if match else None
243
244             elif isinstance(dic[method], (list, tuple)):
245                 # 1..N relationships
246                 match = False
247                 if self.op == contains:
248                     return dic if self.match(dic) else None
249                 else:
250                     subpred = Predicate(subfield, self.op, self.value)
251                     dic[method] = subpred.filter(dic[method])
252                     return dic
253             else:
254                 raise Exception, "Unexpected table format: %r", dic
255
256
257         else:
258             # Individual field operations: this could be simplified, since we are now using operators_short !!
259             # XXX match
260             print "current predicate", self
261             print "matching", dic
262             print "----"
263             return dic if self.match(dic) else None
264
265     def get_field_names(self):
266         if isinstance(self.key, (list, tuple, set, frozenset)):
267             return set(self.key)
268         else:
269             return set([self.key])
270
271     def get_value_names(self):
272         if isinstance(self.value, (list, tuple, set, frozenset)):
273             return set(self.value)
274         else:
275             return set([self.value])
276
277     def has_empty_value(self):
278         if isinstance(self.value, (list, tuple, set, frozenset)):
279             return not any(self.value)
280         else:
281             return not self.value