Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

root/branches/crazycache/dejavu/storage/__init__.py

Revision 555 (checked in by fumanchu, 4 years ago)

Thread-safe memcache.py now in storage.

  • Property svn:eol-style set to native
Line 
1 """Storage Managers for Dejavu."""
2
3 import datetime
4 try:
5     set
6 except NameError:
7     from sets import Set as set
8 import types
9
10 import dejavu
11 from dejavu import errors, logflags, recur, sandboxes, xray
12 from dejavu.containers import Graph
13 from geniusql import logic, astwalk
14
15
16 class StorageManager(object):
17     """A Manager base class for storing and retrieving Units.
18     
19     The base StorageManager class doesn't actually store anything;
20     it needs to be subclassed.
21     
22     ----
23     
24     Regarding large systems:
25     See http://www-db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf
26     
27         "A scale-agnostic programming abstraction must have the notion of
28         entity as the boundary of atomicity."
29     
30     If you want Dejavu to function as a scale-aware lower layer, use Units
31     to represent Pat Helland's "entities", and use them as a boundary for
32     OLTP/CRUD operations like save and delete. DO NOT write StorageManagers
33     which mix multiple entities into a single unit. Likewise, large-scale
34     Dejavu apps should not expect atomic operations across/between Units.
35     
36         "...the scale-agnostic application must manage uncertainty
37         itself using workflow [instead of distributed transactions]
38         if it needs to reach agreement across multiple entities."
39     """
40    
41     def __init__(self, allOptions={}):
42         self.classes = set()
43         self.associations = Graph(directed=False)
44        
45         # TODO: move these somewhere else
46         self.engine_functions = {}
47        
48         self.logflags = logflags.ERROR + logflags.IO
49    
50     def shutdown(self, conflicts='error'):
51         """Shut down all connections to internal storage.
52         
53         conflicts: see errors.conflict.
54         """
55         pass
56    
57     def log(self, message):
58         """Default logger (writes to stdout). Feel free to replace."""
59         if isinstance(message, unicode):
60             print message.encode('utf8')
61         else:
62             print message
63    
64     def new_sandbox(self):
65         """Return a new sandbox object bound to self."""
66         return sandboxes.Sandbox(self)
67    
68     #                               Schemas                               #
69    
70     def register(self, cls):
71         """Assert that Units of class 'cls' will be handled."""
72         try:
73             # hack for db SM's
74             nodename = self.db.name
75         except AttributeError:
76             nodename = self.__class__.__name__
77        
78         if self.logflags & logflags.REGISTER:
79             self.log(logflags.REGISTER.message(nodename, cls))
80        
81         self.classes.add(cls)
82        
83         for ua in cls._associations.itervalues():
84             if getattr(ua, "register", True):
85                 self.associations.connect(cls, ua.farClass)
86    
87     def register_all(self, globals):
88         """Register each subclass of Unit in the given globals."""
89         seen = {}
90         for obj in globals.itervalues():
91             if isinstance(obj, type) and issubclass(obj, dejavu.Unit):
92                 self.register(obj)
93                 seen[obj] = None
94         return seen.keys()
95    
96     def class_by_name(self, classname):
97         """Return the class object for the given classname."""
98         for cls in self.classes:
99             if cls.__name__ == classname:
100                 return cls
101         raise KeyError("No registered class found for '%s'." % classname)
102    
103     def map(self, classes, conflicts='error'):
104         """Map classes to internal storage.
105         
106         conflicts: see errors.conflict.
107         """
108         for cls in classes:
109             if not self.has_storage(cls):
110                 if conflicts == 'repair':
111                     self.create_storage(cls)
112                 else:
113                     errors.conflict(conflicts,
114                                     "%s: no storage found." % cls.__name__)
115    
116     def map_all(self, conflicts='error'):
117         """Map all registered classes to internal storage structures.
118         
119         Although classes can be mapped one at a time, in production it
120         is often more useful to map all classes at application startup.
121         Call this method to do so (but register all classes first).
122         
123         This method is idempotent, but that doesn't mean cheap. Try not
124         to call it very often (once at app startup is usually enough).
125         
126         conflicts: see errors.conflict.
127         """
128         self.map(self.classes, conflicts=conflicts)
129    
130     def create_database(self, conflicts='error'):
131         """Create internal structures for the entire database.
132         
133         conflicts: see errors.conflict.
134         
135         This method will NOT create storage for each class, nor will
136         it create any dependent properties or indexes.
137         """
138         raise NotImplementedError("%s has no create_database method."
139                                   % self.__class__)
140    
141     def drop_database(self, conflicts='error'):
142         """Destroy internal structures for the entire database.
143         
144         conflicts: see errors.conflict.
145         
146         This method will also drop storage for each class, including
147         all properties and indexes.
148         """
149         raise NotImplementedError("%s has no drop_database method."
150                                   % self.__class__)
151    
152     def create_storage(self, cls, conflicts='error'):
153         """Create internal structures for the given class.
154         
155         conflicts: see errors.conflict.
156         
157         This method will also create all dependent properties and indexes.
158         """
159         raise NotImplementedError("%s has no create_storage method."
160                                   % self.__class__)
161    
162     def has_storage(self, cls):
163         """If storage structures exist for the given class, return True."""
164         raise NotImplementedError("%s has no has_storage method."
165                                   % self.__class__)
166    
167     def drop_storage(self, cls, conflicts='error'):
168         """Destroy internal structures for the given class.
169         
170         conflicts: see errors.conflict.
171         
172         This method will also drop all dependent properties and indexes.
173         """
174         raise NotImplementedError("%s has no drop_storage method."
175                                   % self.__class__)
176    
177     def add_property(self, cls, name, conflicts='error'):
178         """Add internal structures for the given property.
179         
180         conflicts: see errors.conflict.
181         """
182         raise NotImplementedError("%s has no add_property method."
183                                   % self.__class__)
184    
185     def has_property(self, cls, name):
186         """If storage structures exist for the given property, return True."""
187         raise NotImplementedError("%s has no has_property method."
188                                   % self.__class__)
189    
190     def drop_property(self, cls, name, conflicts='error'):
191         """Destroy internal structures for the given property.
192         
193         conflicts: see errors.conflict.
194         """
195         raise NotImplementedError("%s has no drop_property method."
196                                   % self.__class__)
197    
198     def rename_property(self, cls, oldname, newname, conflicts='error'):
199         """Rename internal structures for the given property.
200         
201         conflicts: see errors.conflict.
202         """
203         raise NotImplementedError("%s has no rename_property method."
204                                   % self.__class__)
205    
206     def add_index(self, cls, name, conflicts='error'):
207         """Add an index to the given property.
208         
209         conflicts: see errors.conflict.
210         """
211         raise NotImplementedError("%s has no add_index method."
212                                   % self.__class__)
213    
214     def has_index(self, cls, name):
215         """If an index exists for the given property, return True."""
216         raise NotImplementedError("%s has no has_index method."
217                                   % self.__class__)
218    
219     def drop_index(self, cls, name, conflicts='error'):
220         """Destroy any index on the given property.
221         
222         conflicts: see errors.conflict.
223         """
224         raise NotImplementedError("%s has no drop_index method."
225                                   % self.__class__)
226    
227    
228     #                          Data Manipulation                          #
229    
230     def reserve(self, unit):
231         """Reserve storage space for the Unit.
232         
233         The store should call unit.cleanse() if it saves the whole unit
234         state on this call.
235         """
236         raise NotImplementedError
237    
238     def save(self, unit, forceSave=False):
239         """Store the unit's property values."""
240         raise NotImplementedError
241    
242     def destroy(self, unit):
243         """Delete the unit."""
244         raise NotImplementedError
245    
246     def xrecall(self, classes, expr=None, order=None, limit=None, offset=None):
247         """Return an iterable of Units."""
248         if isinstance(classes, dejavu.UnitJoin):
249             for unitrow in self._xmultirecall(classes, expr, order=order,
250                                               limit=limit, offset=offset):
251                 yield unitrow
252             return
253        
254         raise NotImplementedError
255    
256     def recall(self, classes, expr=None, order=None, limit=None, offset=None):
257         """Return a sequence of Unit instances which satisfy the expression."""
258         return [x for x in self.xrecall(classes, expr, order=order,
259                                         limit=limit, offset=offset)]
260    
261     def unit(self, cls, **kwargs):
262         """A single Unit which matches the given kwargs, else None.
263         
264         The first Unit matching the kwargs is returned; if no Units match,
265         None is returned.
266         """
267         try:
268             return self.xrecall(cls, logic.filter(**kwargs), limit=1).next()
269         except StopIteration:
270             return None
271    
272     def _sort_func(self, order):
273         """Return a function (for use with list.sort) from the given order."""
274         if order is None:
275             return None
276         elif isinstance(order, types.FunctionType):
277             order = logic.Expression(order)
278             return OrderDeparser(order).sort_func()
279         elif isinstance(order, logic.Expression):
280             return OrderDeparser(order).sort_func()
281         elif isinstance(order, (list, tuple)):
282             triples = [(0, attr.split(" ", 1)[0], attr.endswith(" DESC"))
283                        for attr in order]
284             def sort_func(x, y):
285                 for index, attr, descending in triples:
286                     xval = getattr(x[index], attr)
287                     if xval is None:
288                         diff = -1
289                     else:
290                         yval = getattr(y[index], attr)
291                         if yval is None:
292                             diff = 1
293                         else:
294                             diff = cmp(xval, yval)
295                     if descending:
296                         diff = -diff
297                     if diff != 0:
298                         return diff
299                 return 0
300             return sort_func
301         else:
302             raise TypeError("The 'order' value %r is not one of the allowed "
303                             "types (list, lambda, None, or Expression)." %
304                             order)
305    
306     def _paginate(self, data, order=None, limit=None, offset=None, single=False):
307         """Manually apply ORDER, LIMIT, and OFFSET operators to a unit stream.
308         
309         data: an iterable of sequences of Unit instances. For example,
310             [(ThingA(), ThingB()), ...]
311         single: if False (the default), yield sequences of units. If True,
312             yield single Units.
313         
314         This is a helper function for those Storage Managers which must
315         provide their own implementation of ORDER, LIMIT, and OFFSET
316         operators. If possible, faster native backends should be used.
317         """
318         if order:
319             # Collapse data iterable into a list.
320             data = list(data)
321             data.sort(self._sort_func(order))
322             data = iter(data)
323         elif offset:
324             raise TypeError("Order argument expected when offset is provided.")
325        
326         try:
327             for x in xrange(offset or 0):
328                 data.next()
329            
330             if limit is not None:
331                 for x in xrange(limit):
332                     if single:
333                         yield data.next()[0]
334                     else:
335                         yield data.next()
336             else:
337                 for unitrow in data:
338                     if single:
339                         yield unitrow[0]
340                     else:
341                         yield unitrow
342         except StopIteration:
343             return
344    
345     #                                Views                                #
346     #
347     # The _combine, view, xview, and _multirecall method given below use
348     # self.recall to retrieve entire Units, and then slice and dice them
349     # in memory. This is quite slow; you should *definitely* override
350     # these with provider-specific methods where possible. They are given
351     # here as a fallback mechanism only.
352    
353     def _combine(self, unitjoin, filters):
354         """Return (flat) rows of Unit objects for the given (recursive) join."""
355         cls1, cls2 = unitjoin.class1, unitjoin.class2
356        
357         if isinstance(cls1, dejavu.UnitJoin):
358             table1 = self._combine(cls1, filters)
359             classlist1 = iter(cls1)
360         else:
361             table1 = [[x] for x in self.recall(cls1, filters[cls1])]
362             classlist1 = [cls1]
363        
364         if isinstance(cls2, dejavu.UnitJoin):
365             table2 = self._combine(cls2, filters)
366             classlist2 = iter(cls2)
367         else:
368             table2 = [[x] for x in self.recall(cls2, filters[cls2])]
369             classlist2 = [cls2]
370        
371         # Find an association between the two halves.
372         ua = None
373         for indexA, clsA in enumerate(classlist1):
374             for indexB, clsB in enumerate(classlist2):
375                 path = unitjoin.path or clsB.__name__
376                 ua = clsA._associations.get(path, None)
377                 if ua:
378                     nearKey, farKey = ua.nearKey, ua.farKey
379                     break
380                 path = unitjoin.path or clsA.__name__
381                 ua = clsB._associations.get(path, None)
382                 if ua:
383                     nearKey, farKey = ua.farKey, ua.nearKey
384                     break
385             if ua: break
386         if ua is None:
387             msg = ("No association found between %s and %s." % (cls1, cls2))
388             raise errors.AssociationError(msg)
389        
390         # Yield rows of Unit instances
391         if unitjoin.leftbiased is None:
392             # INNER JOIN
393             # Flatten the inner generator to iterate over it multiple times.
394             table2 = list(table2)
395             for row1 in table1:
396                 nearVal = getattr(row1[indexA], nearKey)
397                 for row2 in table2:
398                     # Test against join constraint
399                     farVal = getattr(row2[indexB], farKey)
400                     if nearVal == farVal:
401                         yield row1 + row2
402         elif unitjoin.leftbiased is True:
403             # LEFT JOIN
404             # Flatten the inner generator to iterate over it multiple times.
405             table2 = list(table2)
406             for row1 in table1:
407                 nearVal = getattr(row1[indexA], nearKey)
408                 found = False
409                 for row2 in table2:
410                     # Test against join constraint
411                     farVal = getattr(row2[indexB], farKey)
412                     if nearVal == farVal:
413                         yield row1 + row2
414                         found = True
415                 if not found:
416                     # Yield dummy objects for table2
417                     yield row1 + [unit.__class__() for unit in row2]
418         else:
419             # RIGHT JOIN
420             # Flatten the inner generator to iterate over it multiple times.
421             table1 = list(table1)
422             for row2 in table2:
423                 unitB = row2[indexB]
424                 farVal = getattr(unitB, farKey)
425                 found = False
426                 for row1 in table1:
427                     # Test against join constraint
428                     nearVal = getattr(row1[indexA], nearKey)
429                     if nearVal == farVal:
430                         yield row1 + row2
431                         found = True
432                 if not found:
433                     # Yield dummy objects for table1
434                     yield [unit.__class__() for unit in row1] + row2
435    
436     def _xmultirecall(self, classes, expr=None, order=None, limit=None, offset=None):
437         """Yield lists of units of the given classes which match expr."""
438         if not isinstance(expr, logic.Expression):
439             expr = logic.Expression(expr)
440        
441         # TODO: deconstruct expr into a set of subexpr's, one for
442         # each class in classes.
443         filters = dict([(cls, None) for cls in classes])
444        
445         def _combine_inner():
446             for unitrow in self._combine(classes, filters):
447                 if expr(*unitrow):
448                     yield unitrow
449         return self._paginate(_combine_inner(), order, limit, offset)
450    
451     def _multirecall(self, classes, expr=None, order=None, limit=None, offset=None):
452         """Return lists of units which satisfy the expression."""
453         return [t for t in self._xmultirecall(classes, expr=expr, order=order,
454                                               limit=limit, offset=offset)]
455    
456     def xview(self, query, order=None, limit=None, offset=None, distinct=False):
457         """Yield property tuples for the given query."""
458         if not isinstance(query, dejavu.Query):
459             query = dejavu.Query(*query)
460        
461         if self.logflags & logflags.VIEW:
462             self.log(logflags.VIEW.message(query, distinct))
463        
464         expr = query.restriction
465        
466         seen = {}
467        
468         # TODO: deconstruct expr into a set of subexpr's, one for
469         # each class in classes.
470         if isinstance(query.relation, dejavu.UnitJoin):
471             filters = dict([(cls, None) for cls in query.relation])
472             data = self._combine(query.relation, filters)
473             if order:
474                 data = [unit for unit in data]
475                 data.sort(dejavu.sort(order))
476                 data = iter(data)
477            
478             if isinstance(query.attributes, logic.Expression):
479                 def puller():
480                     for unitrow in data:
481                         if expr is None or expr(*unitrow):
482                             datarow = tuple(query.attributes(*unitrow))
483                             if distinct:
484                                 if datarow not in seen:
485                                     yield datarow
486                                     seen[datarow] = None
487                             else:
488                                 yield datarow
489             elif query.attributes is None:
490                 # TODO: What sort order (within each Unit)?
491                 choke
492             else:
493                 def puller():
494                     for unitrow in data:
495                         if expr is None or expr(*unitrow):
496                             datarow = []
497                             for i, attrs in enumerate(query.attributes):
498                                 unit = unitrow[i]
499                                 if attrs is None:
500                                     # Return all attributes (what sort order?)
501                                     choke
502                                 else:
503                                     for attr in attrs:
504                                         datarow.append(getattr(unit, attr))
505                             datarow = tuple(datarow)
506                             if distinct:
507                                 if datarow not in seen:
508                                     yield datarow
509                                     seen[datarow] = None
510                             else:
511                                 yield datarow
512         else:
513             data = self.recall(query.relation, expr)
514             if order:
515                 data.sort(dejavu.sort(order))
516                 data = iter(data)
517            
518             if isinstance(query.attributes, logic.Expression):
519                 def puller():
520                     for unit in data:
521                         if expr is None or expr(unit):
522                             datarow = tuple(query.attributes(unit))
523                             if distinct:
524                                 if datarow not in seen:
525                                     yield datarow
526                                     seen[datarow] = None
527                             else:
528                                 yield datarow
529             elif query.attributes is None:
530                 # Return all attributes (what sort order?)
531                 choke
532             else:
533                 def puller():
534                     for unit in data:
535                         if expr is None or expr(unit):
536                             # Use tuples for hashability.
537                             datarow = tuple([getattr(unit, attr)
538                                              for attr in query.attributes])
539                             if distinct:
540                                 if datarow not in seen:
541                                     yield datarow
542                                     seen[datarow] = None
543                             else:
544                                 yield datarow
545        
546         ordered_data = puller()
547         try:
548             for x in xrange(offset or 0):
549                 ordered_data.next()
550            
551             if limit:
552                 for x in xrange(limit):
553                     yield ordered_data.next()
554             else:
555                 for unit in ordered_data:
556                     yield unit
557         except StopIteration:
558             return
559    
560     def view(self, query, order=None, limit=None, offset=None, distinct=False):
561         """Return tuples of attribute values for the given query."""
562         return [x for x in self.xview(query, order=order, limit=limit,
563                                       offset=offset, distinct=distinct)]
564    
565     def count(self, cls, expr=None):
566         """Number of Units of the given cls which match the given expr."""
567         if cls.identifiers:
568             uniq = cls.identifiers
569         else:
570             uniq = cls._properties.keys()
571         return len(self.view((cls, uniq, expr), distinct=True))
572    
573     def range(self, cls, attr, expr=None):
574         """Distinct, non-None attr values (ordered and continuous, if possible).
575         
576         If the given attribute is a known discrete, ordered type
577         (like int, long, datetime.date), this returns the closed interval:
578             
579             [min(attr), ..., max(attr)]
580         
581         That is, all possible values will be output between min and max,
582         even if they do not appear in the dataset.
583         
584         If the given attribute is not reasonably discrete (e.g., str,
585         unicode, or float) then all distinct, non-None values are returned
586         (sorted, if possible).
587         """
588         query = dejavu.Query(cls, [attr], expr)
589         existing = [x[0] for x in self.xview(query, distinct=True)
590                     if x is not None]
591         if not existing:
592             return []
593        
594         attr_type = getattr(cls, attr).type
595         if issubclass(attr_type, (int, long)):
596             return range(min(existing), max(existing) + 1)
597         else:
598             if issubclass(attr_type, datetime.date):
599                 def date_gen():
600                     start, end = min(existing), max(existing)
601                     for d in range((end + 1) - start):
602                         yield start + datetime.timedelta(d)
603                 return date_gen()
604        
605         try:
606             existing.sort()
607         except TypeError:
608             pass
609        
610         return existing
611    
612     def sum(self, cls, attr, expr=None):
613         """Sum of all non-None values for the given cls.attr."""
614         expr = logic.Expression(lambda x: getattr(x, attr) != None) + expr
615         return sum([row[0] for row in
616                     self.xview(dejavu.Query(cls, (attr,), expr))])
617    
618     #                            Transactions                             #
619    
620     # By default, stores do not support Transactions.
621     # Override these with appropriate methods as you are able.
622     start = None
623     rollback = None
624     commit = None
625
626
627 class OrderDeparser(astwalk.ASTDeparser):
628     """Produce a sort function from a supplied logic.Expression object.
629     
630     Each positional argument in the Expression's function signature will be
631     mapped to 'columns' in the list of lists being sorted.
632     """
633    
634     def __init__(self, expr):
635         self.expr = expr
636         astwalk.ASTDeparser.__init__(self, expr.ast)
637    
638     def sort_func(self):
639         """Walk self and return a function (for use with list.sort)."""
640         root = self.ast.root
641         if not isinstance(root, (astwalk.ast.Tuple, astwalk.ast.List)):
642             raise ValueError("Attribute AST roots must be Tuple or List, "
643                              "not %s" % root.__class__.__name__)
644         triples = [self.walk(term) for term in root.getChildren()]
645        
646         def sort_func(x, y):
647             for index, attr, descending in triples:
648                 xval = getattr(x[index], attr)
649                 if xval is None:
650                     diff = -1
651                 else:
652                     yval = getattr(y[index], attr)
653                     if yval is None:
654                         diff = 1
655                     else:
656                         diff = cmp(xval, yval)
657                 if descending:
658                     diff = -diff
659                 if diff != 0:
660                     return diff
661             return 0
662         return sort_func
663    
664     def walk(self, node):
665         """Walk the AST and return a string of code."""
666         nodetype = node.__class__.__name__
667         method = getattr(self, "visit_" + nodetype)
668         args = node.getChildren()
669         if self.verbose:
670             self.debug(nodetype, args)
671         return method(*args)
672    
673     def visit_Name(self, name):
674         if name in self.ast.args:
675             # We've hit a reference to a positional arg, which in our case
676             # implies a reference to a Unit class.
677             return self.ast.args.index(name)
678         else:
679             # Since lambdas don't support local bindings,
680             # any remaining local name must be a keyword arg.
681             raise TypeError("Keyword args not allowed in order expressions.")
682    
683     def visit_Getattr(self, expr, attrname):
684         expr = self.walk(expr)
685         if isinstance(expr, int):
686             # The name in question refers to a UnitProperty (see visit_Name).
687             # The 'False' value declares this is NOT a descending sort.
688             # self.func__builtin___reversed might modify that to True.
689             return [expr, attrname, False]
690         else:
691             raise TypeError("%r.%r does not reference a positional argument." %
692                             (expr, attrname))
693    
694     def visit_Const(self, value):
695         return value
696    
697     def visit_CallFunc(self, func, *args):
698         # e.g. CallFunc(Name('reversed'), [Getattr(Name('v'), 'Date')], None, None)
699         dstar_args = args[-1]
700         star_args = args[-2]
701        
702         posargs = []
703         kwargs = {}
704         for arg in args[:-2]:
705             if isinstance(arg, astwalk.ast.Keyword):
706                 kwargs[arg.name] = self.walk(arg.value)
707             else:
708                 posargs.append(self.walk(arg))
709        
710         func = self.walk(func)
711        
712         # Handle function objects.
713         if logic.builtins.get(func.__name__, None) is func:
714             dispatch = getattr(self, "builtins_" + func.__name__, None)
715             if dispatch:
716                 return dispatch(*posargs)
717        
718         funcname = func.__module__ + "_" + func.__name__
719         funcname = funcname.replace(".", "_")
720         if funcname.startswith("_"):
721             funcname = "func" + funcname
722         dispatch = getattr(self, funcname, None)
723         if dispatch:
724             return dispatch(*posargs)
725        
726         raise CannotRepresent(func)
727    
728     # --------------------------- Dispatchees --------------------------- #
729    
730     def func__builtin___reversed(self, x):
731         # Assume reversed is always used for DESC ordering.
732         # See visit_Getattr for the [expr, attrname, desc] list.
733         x[2] = True
734         return x
735     # For version of Python which did not possess the 'reversed' builtin.
736     builtins_reversed = func__builtin___reversed
737
738
739
740 class ProxyStorage(StorageManager):
741     """A Storage Manager which passes calls to another Storage Manager.
742     
743     database_scope: if True (the default), create_database and drop_database
744         calls are passed on to self.nextstore. Set this to False when using
745         a Proxy in a cyclic storage graph, where another StorageManager will
746         create the proxied database. For example, if you vertically parition
747         a set of classes so that some classes are cached and some are not:
748         
749                             VerticalPartitioner
750                                     |   \
751                                     |   ObjectCache--RAMStorage
752                                     |   /
753                                    Master
754         
755         ...then set this value to False so that the "Master" StorageManager
756         receives only one create_database call.
757         
758         Future versions of Dejavu may grow "table_scope" or other attributes
759         which work similarly against create/drop_storage/property.
760     """
761    
762     def __init__(self, allOptions={}):
763         StorageManager.__init__(self, allOptions)
764         self.nextstore = allOptions.get('Next Store')
765         self.database_scope = allOptions.get('database_scope', True)
766    
767     def unit(self, cls, **kwargs):
768         """A single Unit which matches the given kwargs, else None.
769         
770         The first Unit matching the kwargs is returned; if no Units match,
771         None is returned.
772         """
773         return self.nextstore.unit(cls, **kwargs)
774    
775     def xrecall(self, classes, expr=None, order=None, limit=None, offset=None):
776         """Return an iterable of Units."""
777         if isinstance(classes, dejavu.UnitJoin):
778             for unitrow in self._xmultirecall(classes, expr, order=order,
779                                               limit=limit, offset=offset):
780                 yield unitrow
781             return
782        
783         cls = classes
784         if self.logflags & logflags.RECALL:
785             self.log(logflags.RECALL.message(cls, expr))
786         for unit in self.nextstore.xrecall(cls, expr, order=order,
787                                            limit=limit, offset=offset):
788             yield unit
789    
790     def save(self, unit, forceSave=False):
791         """Store the unit."""
792         if self.logflags & logflags.SAVE:
793             self.log(logflags.SAVE.message(unit, forceSave))
794         self.nextstore.save(unit, forceSave)
795    
796     def destroy(self, unit):
797         """Delete the unit."""
798         if self.logflags & logflags.DESTROY:
799             self.log(logflags.DESTROY.message(unit))
800         self.nextstore.destroy(unit)
801    
802     def reserve(self, unit):
803         """Reserve storage space for the Unit."""
804         self.nextstore.reserve(unit)
805        
806         # Usually we log ASAP, but here we log after
807         # the unit has had a chance to get an auto ID.
808         if self.logflags & logflags.RESERVE:
809             self.log(logflags.RESERVE.message(unit))
810    
811     def xview(self, query, order=None, limit=None, offset=None, distinct=False):
812         """Yield property tuples for the given query."""
813         if not isinstance(query, dejavu.Query):
814             query = dejavu.Query(*query)
815        
816         if self.logflags & logflags.VIEW:
817             self.log(logflags.VIEW.message(query, distinct))
818         return self.nextstore.xview(query, order=order, limit=limit,
819                                     offset=offset, distinct=distinct)
820    
821     def _xmultirecall(self, classes, expr=None, order=None, limit=None, offset=None):
822         """Full inner join units from each class."""
823         if self.logflags & logflags.RECALL:
824             self.log(logflags.RECALL.message(classes, expr))
825         return self.nextstore._xmultirecall(classes, expr, order, limit, offset)
826    
827     #                               Schemas                               #
828    
829     def map(self, classes, conflicts='error'):
830         """Map classes to internal storage.
831         
832         conflict: see errors.conflict.
833         """
834         self.nextstore.map(classes, conflicts=conflicts)
835    
836     def create_database(self, conflicts='error'):
837         """Create internal structures for the entire database.
838         
839         conflicts: see errors.conflict.
840         
841         This method will NOT create storage for each class, nor will
842         it create any dependent properties or indexes.
843         """
844         if self.logflags & logflags.DDL:
845             self.log(logflags.DDL.message("create database"))
846         if self.database_scope:
847             self.nextstore.create_database(conflicts=conflicts)
848    
849     def drop_database(self, conflicts='error'):
850         """Destroy internal structures for the entire database.
851         
852         conflicts: see errors.conflict.
853         
854         This method will also drop storage for each class, including
855         all properties and indexes.
856         """
857         if self.logflags & logflags.DDL:
858             self.log(logflags.DDL.message("drop database"))
859         if self.database_scope:
860             self.nextstore.drop_database(conflicts=conflicts)
861    
862     def create_storage(self, cls, conflicts='error'):
863         """Create internal structures for the given class.
864         
865         conflicts: see errors.conflict.
866         
867         This method will also create all dependent properties and indexes.
868         """
869         if self.logflags & logflags.DDL:
870             self.log(logflags.DDL.message("create storage %r" % cls))
871         self.nextstore.create_storage(cls)
872    
873     def has_storage(self, cls):
874         """If storage structures exist for the given class, return True."""
875         return self.nextstore.has_storage(cls)
876    
877     def drop_storage(self, cls, conflicts='error'):
878         """Destroy internal structures for the given class.
879         
880         conflicts: see errors.conflict.
881         
882         This method will also drop all dependent properties and indexes.
883         """
884         if self.logflags & logflags.DDL:
885             self.log(logflags.DDL.message("drop storage %r" % cls))
886         self.nextstore.drop_storage(cls, conflicts=conflicts)
887    
888     def add_property(self, cls, name, conflicts='error'):
889         """Add internal structures for the given property.
890         
891         conflicts: see errors.conflict.
892         """
893         if self.logflags & logflags.DDL:
894             self.log(logflags.DDL.message("add property %r %r" % (cls, name)))
895         self.nextstore.add_property(cls, name, conflicts=conflicts)
896    
897     def has_property(self, cls, name):
898         """If storage structures exist for the given property, return True."""
899         return self.nextstore.has_property(cls, name)
900    
901     def drop_property(self, cls, name, conflicts='error'):
902         """Destroy internal structures for the given property.
903         
904         conflicts: see errors.conflict.
905         """
906         if self.logflags & logflags.DDL:
907             self.log(logflags.DDL.message("drop property %r %r" % (cls, name)))
908         self.nextstore.drop_property(cls, name, conflicts=conflicts)
909    
910     def rename_property(self, cls, oldname, newname, conflicts='error'):
911         """Rename internal structures for the given property.
912         
913         conflicts: see errors.conflict.
914         """
915         if self.logflags & logflags.DDL:
916             self.log(logflags.DDL.message("rename property %r from %r to %r" %
917                                  (cls, oldname, newname)))
918         self.nextstore.rename_property(cls, oldname, newname, conflicts=conflicts)
919    
920     def shutdown(self, conflicts='error'):
921         """Shut down all connections to internal storage.
922         
923         conflicts: see errors.conflict.
924         """
925         self.nextstore.shutdown(conflicts=conflicts)
926    
927     def add_index(self, cls, name, conflicts='error'):
928         """Add an index to the given property.
929         
930         conflicts: see errors.conflict.
931         """
932         self.nextstore.add_index(cls, name, conflicts=conflicts)
933    
934     def has_index(self, cls, name):
935         """If an index exists for the given property, return True."""
936         return self.nextstore.has_index(cls, name)
937    
938     def drop_index(self, cls, name, conflicts='error'):
939         """Destroy any index on the given property.
940         
941         conflicts: see errors.conflict.
942         """
943         self.nextstore.drop_index(cls, name, conflicts=conflicts)
944    
945     def start(self, isolation=None):
946         if self.nextstore.start:
947             self.nextstore.start(isolation)
948    
949     def rollback(self):
950         if self.nextstore.rollback:
951             self.nextstore.rollback()
952    
953     def commit(self):
954         if self.nextstore.commit:
955             self.nextstore.commit()
956
957
958 class Version(object):
959    
960     def __init__(self, atoms):
961         if isinstance(atoms, (int, float)):
962             atoms = str(atoms)
963         if isinstance(atoms, basestring):
964             import re
965             self.atoms = re.split(r'\W', atoms)
966         else:
967             self.atoms = [str(x) for x in atoms]
968    
969     def __str__(self):
970         return ".".join([str(x) for x in self.atoms])
971    
972     def __cmp__(self, other):
973         cls = self.__class__
974         if not isinstance(other, cls):
975             # Try to coerce other to a Version instance.
976             other = cls(other)
977        
978         index = 0
979         while index < len(self.atoms) and index < len(other.atoms):
980             mine, theirs = self.atoms[index], other.atoms[index]
981             if mine.isdigit() and theirs.isdigit():
982                 mine, theirs = int(mine), int(theirs)
983             if mine < theirs:
984                 return -1
985             if mine > theirs:
986                 return 1
987             index += 1
988         if index < len(other.atoms):
989             return -1
990         if index < len(self.atoms):
991             return 1
992         return 0
993
994
995 managers = {
996     "aged": "dejavu.storage.caching.AgedCache",
997     "cache": "dejavu.storage.caching.ObjectCache",
998     "caching": "dejavu.storage.caching.ObjectCache",
999     "burned": "dejavu.storage.caching.BurnedCache",
1000     "proxy": ProxyStorage,
1001    
1002     "access": "dejavu.storage.storeado.StorageManagerADO_MSAccess",
1003     "msaccess": "dejavu.storage.storeado.StorageManagerADO_MSAccess",
1004    
1005     "firebird": "dejavu.storage.storefirebird.StorageManagerFirebird",
1006     "mysql": "dejavu.storage.storemysql.StorageManagerMySQL",
1007    
1008     "postgres": "dejavu.storage.storepypgsql.StorageManagerPgSQL",
1009     "postgresql": "dejavu.storage.storepypgsql.StorageManagerPgSQL",
1010     "pypgsql": "dejavu.storage.storepypgsql.StorageManagerPgSQL",
1011    
1012     "psycopg": "dejavu.storage.storepsycopg.StorageManagerPsycoPg",
1013     "psycopg2": "dejavu.storage.storepsycopg.StorageManagerPsycoPg",
1014    
1015     "ram": "dejavu.storage.storeram.RAMStorage",
1016     "shelve": "dejavu.storage.storeshelve.StorageManagerShelve",
1017     "sqlite": "dejavu.storage.storesqlite.StorageManagerSQLite",
1018    
1019     "sqlserver": "dejavu.storage.storeado.StorageManagerADO_SQLServer",
1020     "mssql": "dejavu.storage.storeado.StorageManagerADO_SQLServer",
1021    
1022     "folders": "dejavu.storage.storefs.StorageManagerFolders",
1023    
1024     "json": "dejavu.storage.storejson.StorageManagerJSON",
1025    
1026     "memcache": "dejavu.storage.storememcached.MemcachedStorageManager",
1027     "memcached": "dejavu.storage.storememcached.MemcachedStorageManager",
1028     }
1029
1030
1031 def resolve(store, options=None):
1032     """Return a StorageManager for the given name, classname, class, or SM."""
1033     if isinstance(store, basestring):
1034         if store in managers:
1035             store = managers[store]
1036    
1037     if isinstance(store, basestring):
1038         store = xray.classes(store)(options or {})
1039     else:
1040         import types
1041         if isinstance(store, (type, types.ClassType)):
1042             store = store(options or {})
1043    
1044     return store
1045
Note: See TracBrowser for help on using the browser.