| 1 |
"""OLAP Views and Snapshots (materialized views). |
|---|
| 2 |
|
|---|
| 3 |
Notice in particular that Snapshot and View are _temporary_ Units. |
|---|
| 4 |
Even when you memorize them, they won't be persistent unless you set |
|---|
| 5 |
each instance's Expiration to None. |
|---|
| 6 |
""" |
|---|
| 7 |
|
|---|
| 8 |
import datetime |
|---|
| 9 |
import dejavu |
|---|
| 10 |
from dejavu import errors, recur |
|---|
| 11 |
from geniusql import logic |
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
class TemporaryUnit(dejavu.Unit): |
|---|
| 15 |
|
|---|
| 16 |
Expiration = dejavu.UnitProperty(datetime.datetime) |
|---|
| 17 |
|
|---|
| 18 |
def on_recall(self): |
|---|
| 19 |
if self.Expiration is not None: |
|---|
| 20 |
if self.Expiration <= datetime.datetime.now(): |
|---|
| 21 |
self.forget() |
|---|
| 22 |
raise errors.UnrecallableError |
|---|
| 23 |
else: |
|---|
| 24 |
self.decay(minutes=15) |
|---|
| 25 |
|
|---|
| 26 |
def decay(self, **kw): |
|---|
| 27 |
"""decay(**kw) -> Set Expiration to now() + timedelta(**kw).""" |
|---|
| 28 |
self.Expiration = datetime.datetime.now() + datetime.timedelta(**kw) |
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 |
class TemporarySweeper(recur.Worker): |
|---|
| 32 |
"""A worker to sweep out expired TemporaryUnit's.""" |
|---|
| 33 |
|
|---|
| 34 |
def work(self): |
|---|
| 35 |
"""Start a cycle of scheduled work.""" |
|---|
| 36 |
now = datetime.datetime.now() |
|---|
| 37 |
f = lambda x: x.Expiration != None and x.Expiration <= now |
|---|
| 38 |
box = self.store.new_sandbox() |
|---|
| 39 |
|
|---|
| 40 |
for cls in self.store.classes: |
|---|
| 41 |
if issubclass(cls, TemporaryUnit): |
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 |
box.recall(cls, f) |
|---|
| 45 |
box.flush_all() |
|---|
| 46 |
|
|---|
| 47 |
|
|---|
| 48 |
class View(TemporaryUnit): |
|---|
| 49 |
"""A selector from (possibly multiple and/or aggregated) relations. |
|---|
| 50 |
|
|---|
| 51 |
When a View is called, it returns a Snapshot. |
|---|
| 52 |
""" |
|---|
| 53 |
|
|---|
| 54 |
Name = dejavu.UnitProperty() |
|---|
| 55 |
Created = dejavu.UnitProperty(datetime.datetime) |
|---|
| 56 |
|
|---|
| 57 |
|
|---|
| 58 |
|
|---|
| 59 |
Query = dejavu.UnitProperty(dejavu.Query) |
|---|
| 60 |
|
|---|
| 61 |
def __init__(self, Query=None, Created=None, **kwargs): |
|---|
| 62 |
if Created is None: |
|---|
| 63 |
Created = datetime.datetime.today() |
|---|
| 64 |
if not isinstance(Query, dejavu.Query): |
|---|
| 65 |
Query = dejavu.Query(*Query) |
|---|
| 66 |
TemporaryUnit.__init__(self, Query=Query, Created=Created, **kwargs) |
|---|
| 67 |
|
|---|
| 68 |
def on_forget(self): |
|---|
| 69 |
|
|---|
| 70 |
for mv in self.Snapshot(): |
|---|
| 71 |
mv.forget() |
|---|
| 72 |
|
|---|
| 73 |
def __call__(self, name, store=None): |
|---|
| 74 |
"""Execute self and return a Snapshot.""" |
|---|
| 75 |
if store is None: |
|---|
| 76 |
store = self.sandbox.store |
|---|
| 77 |
newcls = store.insert_into(name, self.Query) |
|---|
| 78 |
snap = Snapshot(ViewID=self.ID, ResultName=name) |
|---|
| 79 |
snap.decay(minutes=15) |
|---|
| 80 |
return snap |
|---|
| 81 |
results = __call__ |
|---|
| 82 |
|
|---|
| 83 |
def __copy__(self): |
|---|
| 84 |
view = TemporaryUnit.__copy__(self) |
|---|
| 85 |
view.Name = "Copy of %s" % self.Name |
|---|
| 86 |
view.Created = datetime.datetime.now() |
|---|
| 87 |
return view |
|---|
| 88 |
|
|---|
| 89 |
|
|---|
| 90 |
class Snapshot(TemporaryUnit): |
|---|
| 91 |
"""A materialized view; the result set obtained by calling a View. |
|---|
| 92 |
|
|---|
| 93 |
Each Snapshot Unit instance possesses its own additional, dynamic |
|---|
| 94 |
unit class which contains the actual result data. The 'ResultName' |
|---|
| 95 |
attribute of the Snapshot must match the class name of the actual |
|---|
| 96 |
result data. |
|---|
| 97 |
""" |
|---|
| 98 |
|
|---|
| 99 |
ViewID = dejavu.UnitProperty(int, index=True) |
|---|
| 100 |
Timestamp = dejavu.UnitProperty(datetime.datetime) |
|---|
| 101 |
ResultName = dejavu.UnitProperty() |
|---|
| 102 |
|
|---|
| 103 |
def __init__(self, Timestamp=None, **kwargs): |
|---|
| 104 |
if Timestamp is None: |
|---|
| 105 |
Timestamp = datetime.datetime.now() |
|---|
| 106 |
TemporaryUnit.__init__(self, Timestamp=Timestamp, **kwargs) |
|---|
| 107 |
|
|---|
| 108 |
def unitclass(self, store=None): |
|---|
| 109 |
if store is None: |
|---|
| 110 |
store = self.sandbox.store |
|---|
| 111 |
try: |
|---|
| 112 |
return store.class_by_name(self.ResultName) |
|---|
| 113 |
except KeyError: |
|---|
| 114 |
newclass = store.make_class(self.ResultName) |
|---|
| 115 |
store.register(newclass) |
|---|
| 116 |
return newclass |
|---|
| 117 |
|
|---|
| 118 |
|
|---|
| 119 |
View.one_to_many('ID', Snapshot, 'ViewID') |
|---|
| 120 |
|
|---|
| 121 |
|
|---|
| 122 |
def register_classes(store): |
|---|
| 123 |
store.register(View) |
|---|
| 124 |
store.register(Snapshot) |
|---|
| 125 |
|
|---|