Package Pyblio :: Module Query
[hide private]
[frames] | no frames]

Source Code for Module Pyblio.Query

  1  # This file is part of pybliographer 
  2  #  
  3  # Copyright (C) 1998-2006 Frederic GOBRY 
  4  # Email : gobry@pybliographer.org 
  5  #           
  6  # This program is free software; you can redistribute it and/or 
  7  # modify it under the terms of the GNU General Public License 
  8  # as published by the Free Software Foundation; either version 2  
  9  # of the License, or (at your option) any later version. 
 10  #    
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details.  
 15  #  
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 19   
 20  """ Definition of the query language. 
 21   
 22  A query is composed of elementary search operations, which are 
 23  combined by boolean operations: 
 24   
 25     >>> query = (AnyWord(u'findme') | Txo('type', item)) & ~ HasField('title') 
 26   
 27  """ 
 28   
 29  from Pyblio import Attribute, Exceptions 
 30  from Pyblio.Arrays import KeyArray, match_arrays 
 31   
 32   
33 -class _Constraint(object):
34
35 - def __and__(self, other):
36 37 return _ANDed(self, other)
38
39 - def __or__(self, other):
40 41 return _ORed(self, other)
42
43 - def __invert__(self):
44 45 return _NOTed(self)
46
47 - def apply (self, fn, *args, **kargs):
48 49 fn(self, *args, ** kargs) 50 return
51
52 - def __len__(self):
53 54 return 1
55 56 57
58 -class _NOTed(_Constraint):
59
60 - def __init__(self, a):
61 62 self.a = a 63 return
64
65 - def __str__(self):
66 return '~ %s' % str(self.a)
67
68 - def apply (self, fn, *args, **kargs):
69 70 self.a.apply(fn, *args, **kargs)
71
72 - def run(self, db):
73 74 ra = self.a.run(db) 75 rb = db._q_all() 76 77 a, b = match_arrays(ra.a, rb.a) 78 79 return KeyArray(a=~a & b)
80 81
82 -class _Pairs(_Constraint):
83
84 - def apply(self, fn, *args, **kargs):
85 86 self.a.apply(fn, *args, **kargs) 87 self.b.apply(fn, *args, **kargs) 88 return
89
90 - def __len__(self):
91 92 return len(self.a) + len(self.b)
93 94
95 -class _ORed(_Pairs):
96
97 - def __init__(self, a, b):
98 99 self.a = a 100 self.b = b 101 return
102
103 - def __str__(self):
104 return '(%s | %s)' % (str(self.a), 105 str(self.b))
106
107 - def run(self, db):
108 109 ra = self.a.run(db) 110 rb = self.b.run(db) 111 112 a, b = match_arrays(ra.a, rb.a) 113 114 return KeyArray(a=a | b)
115
116 -class _ANDed(_Pairs):
117
118 - def __init__(self, a, b):
119 120 self.a = a 121 self.b = b 122 return
123
124 - def __str__(self):
125 return '(%s & %s)' % (str(self.a), 126 str(self.b))
127
128 - def run(self, db):
129 130 ra = self.a.run(db) 131 rb = self.b.run(db) 132 133 a, b = match_arrays(ra.a, rb.a) 134 135 return KeyArray(a=a & b)
136 137
138 -class Null(_Constraint):
139 """ Does not search anything, but is useful when programatically 140 constructing a query: 141 142 >>> q = Null() 143 >>> q = q & AnyWord(...) 144 145 """ 146
147 - def __and__(self, other):
148 return other
149
150 - def __or__(self, other):
151 return other
152
153 - def run(self, db):
154 return db._q_all()
155 156
157 -class AnyWord(_Constraint):
158 159 """ Full text searching of a single word """ 160
161 - def __init__(self, word):
162 163 self.word = word 164 return
165 166
167 - def validate(self, schema):
168 return
169
170 - def run(self, db):
171 return db._q_anyword(self)
172
173 - def __str__(self):
174 return 'AnyWord(%s)' % repr(self.word)
175
176 -class HasField(_Constraint):
177 178 """ Matches when the record has the specified field.""" 179
180 - def __init__(self, field):
181 self.field = field 182 return
183
184 - def validate(self, schema):
185 186 try: 187 t = schema[self.field] 188 189 except KeyError: 190 raise Exceptions.InvalidQuery('unknown field: %s' % self.field) 191 192 return
193
194 - def run(self, db):
195 return db._q_hasfield(self)
196 197
198 -class Txo(_Constraint):
199 200 """ Search items that belong to the corresponding txo """ 201 202 Attr = Attribute.Txo 203
204 - def __init__(self, field, txo):
205 206 self.field = field 207 self.txo = txo 208 return
209
210 - def validate(self, schema):
211 212 try: 213 t = schema[self.field].type 214 215 except KeyError: 216 raise Exceptions.InvalidQuery ('unknown field: %s' % self.field) 217 218 if t is not self.Attr: 219 raise Exceptions.InvalidQuery ('invalid field type: %s' % self.field) 220 221 return
222
223 - def run(self, db):
224 return db._q_txo(self)
225 226
227 -class Queryable(object):
228 229 """ A mixin that provides an (one day optimized) query engine to a store """ 230
231 - def query(self, query):
232 """ Perform a query and return a result set of the matching records. """ 233 self._q_check(query) 234 235 r = query.run(self) 236 return self._q_to_rs(r)
237 238
239 - def count(self, query):
240 """ Perform a query and return the count of matching records. """ 241 242 self._q_check(query) 243 244 return len(query.run(self))
245 246 247 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 248 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 249
250 - def _q_check(self, query):
251 252 # Check if the query is well typed 253 def check(const, schema): 254 const.validate(schema) 255 return
256 257 query.apply(check, self.schema) 258 return
259 260 261 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 262
263 - def _q_hasfield(self, q):
264 265 res = KeyArray() 266 267 for e in self.entries.itervalues(): 268 269 try: 270 fs = e[q.field] 271 272 except KeyError: 273 continue 274 275 res.add(e.key) 276 277 return res
278
279 - def _q_txo(self, q):
280 281 res = KeyArray() 282 283 full = self.schema.txo[q.txo.group].expand(q.txo.id) 284 285 for e in self.entries.itervalues(): 286 287 try: 288 fs = e[q.field] 289 290 except KeyError: 291 continue 292 293 for f in fs: 294 if f.id in full: 295 res.add(e.key) 296 break 297 298 return res
299
300 - def _q_anyword(self, q):
301 302 res = KeyArray() 303 304 word = q.word.lower() 305 306 for entry in self.entries.itervalues(): 307 308 found = False 309 310 for attrs in entry.values (): 311 312 for attr in attrs: 313 idx = attr.index () 314 315 if word in idx: 316 found = True 317 break 318 319 if found: break 320 321 if not found: continue 322 323 res.add(entry.key) 324 325 return res
326 327 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 328
329 - def _q_to_rs(self, res):
330 331 rs = self.rs.new() 332 333 for key in res: 334 rs.add(key) 335 336 return rs
337
338 - def _q_all(self):
339 340 r = KeyArray() 341 342 for k in self.entries: 343 r.add(k) 344 345 return r
346