1 from types import StringTypes
9 import datetime # Jordan
10 #from manifold.util.parameter import Parameter, Mixed, python_type
11 from manifold.util.predicate import Predicate, eq
12 from itertools import ifilter
16 A filter is a set of predicates
19 #def __init__(self, s=()):
20 # super(Filter, self).__init__(s)
27 f.add(Predicate(*element))
29 print "Error in setting Filter from list", e
36 for key, value in d.items():
37 if key[0] in Predicate.operators.keys():
38 f.add(Predicate(key[1:], key[0], value))
40 f.add(Predicate(key, '=', value))
45 for predicate in self:
46 ret.append(predicate.to_list())
51 def from_clause(clause):
53 NOTE: We can only handle simple clauses formed of AND fields.
55 raise Exception, "Not implemented"
57 def filter_by(self, predicate):
62 return ' AND '.join([str(pred) for pred in self])
65 return '<Filter: %s>' % ' AND '.join([str(pred) for pred in self])
68 return tuple([hash(pred) for pred in self])
71 return hash(self.__key())
73 def __additem__(self, value):
74 if value.__class__ != Predicate:
75 raise TypeError("Element of class Predicate expected, received %s" % value.__class__.__name__)
76 set.__additem__(self, value)
79 return set([x.key for x in self])
81 # XXX THESE FUNCTIONS SHOULD ACCEPT MULTIPLE FIELD NAMES
89 def has_op(self, key, op):
91 if x.key == key and x.op == op:
95 def has_eq(self, key):
96 return self.has_op(key, eq)
105 def delete(self, key):
113 #self = filter(lambda x: x.key != key, self)
115 def get_op(self, key, op):
116 if isinstance(op, (list, tuple, set)):
118 if x.key == key and x.op in op:
122 if x.key == key and x.op == op:
126 def get_eq(self, key):
127 return self.get_op(key, eq)
129 def set_op(self, key, op, value):
131 if x.key == key and x.op == op:
136 def set_eq(self, key, value):
137 return self.set_op(key, eq, value)
139 def get_predicates(self, key):
140 # XXX Would deserve returning a filter (cf usage in SFA gateway)
147 # def filter(self, dic):
148 # # We go through every filter sequentially
149 # for predicate in self:
150 # print "predicate", predicate
151 # dic = predicate.filter(dic)
154 def match(self, dic, ignore_missing=True):
155 for predicate in self:
156 if not predicate.match(dic, ignore_missing):
167 def get_field_names(self):
169 for predicate in self:
170 field_names |= predicate.get_field_names()
173 #class OldFilter(Parameter, dict):
175 # A type of parameter that represents a filter on one or more
176 # columns of a database table.
177 # Special features provide support for negation, upper and lower bounds,
178 # as well as sorting and clipping.
181 # fields should be a dictionary of field names and types.
182 # As of PLCAPI-4.3-26, we provide support for filtering on
183 # sequence types as well, with the special '&' and '|' modifiers.
184 # example : fields = {'node_id': Parameter(int, "Node identifier"),
185 # 'hostname': Parameter(int, "Fully qualified hostname", max = 255),
189 # filter should be a dictionary of field names and values
190 # representing the criteria for filtering.
191 # example : filter = { 'hostname' : '*.edu' , site_id : [34,54] }
192 # Whether the filter represents an intersection (AND) or a union (OR)
193 # of these criteria is determined by the join_with argument
194 # provided to the sql method below
198 # * a field starting with '&' or '|' should refer to a sequence type
199 # the semantic is then that the object value (expected to be a list)
200 # should contain all (&) or any (|) value specified in the corresponding
201 # filter value. See other examples below.
202 # example : filter = { '|role_ids' : [ 20, 40 ] }
203 # example : filter = { '|roles' : ['tech', 'pi'] }
204 # example : filter = { '&roles' : ['admin', 'tech'] }
205 # example : filter = { '&roles' : 'tech' }
207 # * a field starting with the ~ character means negation.
208 # example : filter = { '~peer_id' : None }
210 # * a field starting with < [ ] or > means lower than or greater than
211 # < > uses strict comparison
212 # [ ] is for using <= or >= instead
213 # example : filter = { ']event_id' : 2305 }
214 # example : filter = { '>time' : 1178531418 }
215 # in this example the integer value denotes a unix timestamp
217 # * if a value is a sequence type, then it should represent
218 # a list of possible values for that field
219 # example : filter = { 'node_id' : [12,34,56] }
221 # * a (string) value containing either a * or a % character is
222 # treated as a (sql) pattern; * are replaced with % that is the
223 # SQL wildcard character.
224 # example : filter = { 'hostname' : '*.jp' }
226 # * the filter's keys starting with '-' are special and relate to sorting and clipping
227 # * '-SORT' : a field name, or an ordered list of field names that are used for sorting
228 # these fields may start with + (default) or - for denoting increasing or decreasing order
229 # example : filter = { '-SORT' : [ '+node_id', '-hostname' ] }
230 # * '-OFFSET' : the number of first rows to be ommitted
231 # * '-LIMIT' : the amount of rows to be returned
232 # example : filter = { '-OFFSET' : 100, '-LIMIT':25}
234 # Here are a few realistic examples
236 # GetNodes ( { 'node_type' : 'regular' , 'hostname' : '*.edu' , '-SORT' : 'hostname' , '-OFFSET' : 30 , '-LIMIT' : 25 } )
237 # would return regular (usual) nodes matching '*.edu' in alphabetical order from 31th to 55th
239 # GetPersons ( { '|role_ids' : [ 20 , 40] } )
240 # would return all persons that have either pi (20) or tech (40) roles
242 # GetPersons ( { '&role_ids' : 10 } )
243 # GetPersons ( { '&role_ids' : 10 } )
244 # GetPersons ( { '|role_ids' : [ 10 ] } )
245 # GetPersons ( { '|role_ids' : [ 10 ] } )
246 # all 4 forms are equivalent and would return all admin users in the system
249 # def __init__(self, fields = {}, filter = {}, doc = "Attribute filter"):
250 # # Store the filter in our dict instance
251 # dict.__init__(self, filter)
253 # # Declare ourselves as a type of parameter that can take
254 # # either a value or a list of values for each of the specified
256 # self.fields = dict ( [ ( field, Mixed (expected, [expected]))
257 # for (field,expected) in fields.iteritems() ] )
259 # # Null filter means no filter
260 # Parameter.__init__(self, self.fields, doc = doc, nullok = True)
262 # def sql(self, api, join_with = "AND"):
264 # Returns a SQL conditional that represents this filter.
267 # # So that we always return something
268 # if join_with == "AND":
269 # conditionals = ["True"]
270 # elif join_with == "OR":
271 # conditionals = ["False"]
273 # assert join_with in ("AND", "OR")
279 # for field, value in self.iteritems():
280 # # handle negation, numeric comparisons
281 # # simple, 1-depth only mechanism
283 # modifiers={'~' : False,
284 # '<' : False, '>' : False,
285 # '[' : False, ']' : False,
287 # '&' : False, '|' : False,
290 # def check_modifiers(field):
291 # if field[0] in modifiers.keys():
292 # modifiers[field[0]] = True
294 # return check_modifiers(field)
296 # field = check_modifiers(field)
299 # if not modifiers['-']:
300 # if field not in self.fields:
301 # raise PLCInvalidArgument, "Invalid filter field '%s'" % field
303 # # handling array fileds always as compound values
304 # if modifiers['&'] or modifiers['|']:
305 # if not isinstance(value, (list, tuple, set)):
308 # if isinstance(value, (list, tuple, set)):
309 # # handling filters like '~slice_id':[]
310 # # this should return true, as it's the opposite of 'slice_id':[] which is false
311 # # prior to this fix, 'slice_id':[] would have returned ``slice_id IN (NULL) '' which is unknown
312 # # so it worked by coincidence, but the negation '~slice_ids':[] would return false too
314 # if modifiers['&'] or modifiers['|']:
322 # value = map(str, map(api.db.quote, value))
325 # value = "ARRAY[%s]" % ", ".join(value)
326 # elif modifiers['|']:
328 # value = "ARRAY[%s]" % ", ".join(value)
331 # value = "(%s)" % ", ".join(value)
336 # elif isinstance(value, StringTypes) and \
337 # (value.find("*") > -1 or value.find("%") > -1):
339 # # insert *** in pattern instead of either * or %
340 # # we dont use % as requests are likely to %-expansion later on
341 # # actual replacement to % done in PostgreSQL.py
342 # value = value.replace ('*','***')
343 # value = value.replace ('%','***')
344 # value = str(api.db.quote(value))
356 # # value = str(api.db.quote(value))
358 # if isinstance(value, StringTypes) and value[-2:] != "()": # XXX
359 # value = str(api.db.quote(value))
360 # if isinstance(value, datetime.datetime):
361 # value = str(api.db.quote(str(value)))
364 # # field = "%s.%s" % (prefix,field)
366 # clause = "\"%s\" %s %s" % (field, operator, value)
368 # clause = "%s %s %s" % (field, operator, value)
371 # clause = " ( NOT %s ) " % (clause)
373 # conditionals.append(clause)
374 # # sorting and clipping
376 # if field not in ('SORT','OFFSET','LIMIT'):
377 # raise PLCInvalidArgument, "Invalid filter, unknown sort and clip field %r"%field
379 # if field == 'SORT':
380 # if not isinstance(value,(list,tuple,set)):
382 # for field in value:
384 # if field[0] == '+':
386 # elif field[0] == '-':
389 # if field not in self.fields:
390 # raise PLCInvalidArgument, "Invalid field %r in SORT filter"%field
391 # sorts.append("%s %s"%(field,order))
393 # elif field == 'OFFSET':
394 # clips.append("OFFSET %d"%value)
395 # # clipping continued
396 # elif field == 'LIMIT' :
397 # clips.append("LIMIT %d"%value)
399 # where_part = (" %s " % join_with).join(conditionals)
402 # clip_part += " ORDER BY " + ",".join(sorts)
404 # clip_part += " " + " ".join(clips)
405 ## print 'where_part=',where_part,'clip_part',clip_part
406 # return (where_part,clip_part)