*** empty log message ***
[pli.git] / pli / persistance / sql / core.py
blobc7e1f68b3a7509dcb07668ae09320ebbb6137bf4
1 #=======================================================================
3 __version__ = '''0.0.01'''
4 __sub_version__ = '''20050824030618'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
10 import pickle
11 import sys
12 import types
16 #-----------------------------------------------------------------------
17 #-------------------------------------------------------transactioned---
18 # XXX transaction decorator...
19 def transactioned():
20 '''
21 '''
22 return
26 #-----------------------------------------------------------------------
27 #-------------------------------------------------registertypehandler---
28 # XXX can this be made into a generic dispatch decorator???
29 def registertypehandler(type):
30 '''
31 '''
32 handlers = sys._getframe(1).f_locals['_typehandlers']
33 def handler(func):
34 handlers[type] = func
35 return func
36 return handler
39 #-----------------------------------------------------------SQLWriter---
40 ##!! REVISE !!##
41 ##!!! ADD FUNCTIONS AND OTHER TYPES (fallback to pickle) !!!##
42 # XXX this is not able to pickle extension types... (fix?)
43 # TODO make an atomic type handler constructor... (should this be
44 # decoratable??)
45 # TODO make ALL of thefolowing packable into transactions...
46 # XXX needs more pedantic checking...
47 # XXX add value check for mutable objects... (if value exists then
48 # return old id...)
49 class SQLWriter(object):
50 '''
51 '''
52 _typehandlers = {}
54 __object_native_attrs__ = ('__dict__', '__class__')
56 def __init__(self, sql):
57 '''
58 '''
59 self.sql = sql
60 # atomic type handlers...
61 @registertypehandler(int)
62 def do_int(self, o, oid=None):
63 '''
64 '''
65 obj_id = self.sql.insert('py_object', type='py_int').lastrowid
66 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
67 self.sql.insert('py_int', pyoid=obj_id, value=o)
68 return obj_id
69 @registertypehandler(long)
70 def do_long(self, o, oid=None):
71 '''
72 '''
73 obj_id = self.sql.insert('py_object', type='py_long').lastrowid
74 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
75 self.sql.insert('py_long', pyoid=obj_id, value=o)
76 return obj_id
77 @registertypehandler(float)
78 def do_float(self, o, oid=None):
79 '''
80 '''
81 obj_id = self.sql.insert('py_object', type='py_float').lastrowid
82 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
83 self.sql.insert('py_float', pyoid=obj_id, value=o)
84 return obj_id
85 ## @registertypehandler(complex)
86 ## def do_complex(self, o, oid=None):
87 ## '''
88 ## '''
89 ## pass
90 @registertypehandler(str)
91 def do_str(self, o, oid=None):
92 '''
93 '''
94 obj_id = self.sql.insert('py_object', type='py_str').lastrowid
95 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
96 self.sql.insert('py_str', pyoid=obj_id, value=o)
97 return obj_id
98 @registertypehandler(unicode)
99 def do_unicode(self, o, oid=None):
102 obj_id = self.sql.insert('py_object', type='py_unicode').lastrowid
103 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
104 self.sql.insert('py_unicode', pyoid=obj_id, value=o)
105 return obj_id
106 @registertypehandler(tuple)
107 def do_tuple(self, tpl, oid=None):
110 py_tuple row format:
111 pyoid(oid) -> py_object.pyoid
113 py_tuple_items row format:
114 pyoid(oid) -> py_tuple.pyoid
115 order(int)
116 value(oid) -> py_object.pyoid
118 obj_id = self.sql.insert('py_object', type='py_tuple').lastrowid
119 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
120 self.sql.insert('py_tuple', pyoid=obj_id)
121 for i, o in enumerate(tpl):
122 # insert the element...
123 item_id = self.write(o)
124 self.sql.insert('py_tuple_item', order=i, pyoid=obj_id, value=item_id)
125 return obj_id
126 # mutable handlers...
127 @registertypehandler(list)
128 def do_list(self, lst, oid=None):
131 py_list row format:
132 pyoid(oid) -> py_object.pyoid
134 py_list_items row format:
135 pyoid(oid) -> py_list.pyoid
136 order(int)
137 value(oid) -> py_object.pyoid
139 NOTE: if object id (oid) is given, the object will be updated.
141 if oid != None:
142 # XXX use the strategy of "keep the existing, add the new,
143 # remove the old."
144 self.sql.delete('py_list_item', self.sql.where(pyoid=oid))
145 obj_id = oid
146 else:
147 obj_id = self.sql.insert('py_object', type='py_list').lastrowid
148 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
149 self.sql.insert('py_list', pyoid=obj_id)
150 # insert the list items...
151 for i, o in enumerate(lst):
152 item_id = self.write(o)
153 self.sql.insert('py_list_item', order=i, pyoid=obj_id, value=item_id)
154 return obj_id
155 @registertypehandler(dict)
156 def do_dict(self, dct, oid=None):
159 py_dict row format:
160 pyoid(oid) -> py_object.pyoid
162 py_dict_items row format:
163 pyoid(oid) -> py_dict.pyoid
164 key(oid) -> py_object.pyoid
165 value(oid) -> py_object.pyoid
167 NOTE: if object id (oid) is given, the object will be updated.
169 if oid != None:
170 # XXX use the strategy of "keep the existing, add the new,
171 # remove the old."
172 self.sql.delete('py_dict_item', self.sql.where(pyoid=oid))
173 obj_id = oid
174 else:
175 obj_id = self.sql.insert('py_object', type='py_dict').lastrowid
176 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
177 self.sql.insert('py_dict', pyoid=obj_id)
178 # insert the items...
179 for k, v in dct.items():
180 key_id = self.write(k)
181 val_id = self.write(v)
182 self.sql.insert('py_dict_item', pyoid=obj_id, key=key_id, value=val_id)
183 return obj_id
184 @registertypehandler(object)
185 def do_object(self, obj, oid=None):
188 NOTE: if object id (oid) is given, the object will be updated.
190 if oid != None:
191 # XXX use the strategy of "keep the existing, add the new,
192 # remove the old."
193 self.sql.delete('py_object_attribute', self.sql.where(pyoid=oid))
194 obj_id = oid
195 else:
196 obj_id = self.sql.insert('py_object').lastrowid
197 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
199 for n in self.__object_native_attrs__:
200 # insert the element...
201 ## name_id = self.write(n)
202 ## self.sql.insert('py_object_attribute', pyoid=obj_id, name=name_id, value=val_id)
203 val_id = self.write(getattr(obj, n))
204 self.sql.insert('py_object_attribute', pyoid=obj_id, name=n, value=val_id)
205 return obj_id
206 # pickle handlers...
207 @registertypehandler(type)
208 def do_class(self, cls, oid=None):
211 obj_id = self.sql.insert('py_object', type='py_pickled_class').lastrowid
212 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
214 self.sql.insert('py_pickled_class',
215 pyoid=obj_id,
216 pickle=pickle.dumps(cls))
217 return obj_id
218 @registertypehandler(types.FunctionType)
219 def do_function(self, cls, oid=None):
222 obj_id = self.sql.insert('py_object', type='py_pickled_function').lastrowid
223 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
225 self.sql.insert('py_pickled_function',
226 pyoid=obj_id,
227 pickle=pickle.dumps(cls))
228 return obj_id
229 # HL interface methods...
230 # XXX make this support the pickle protocols...
231 def write(self, obj, oid=None):
234 t = type(obj)
235 handler = self._typehandlers.get(t, None)
236 if handler is None:
237 return self.do_object(obj, oid)
238 return handler(self, obj, oid)
242 #-----------------------------------------------------------------------
243 #--------------------------------------------------------------Object---
244 # WARNING: do not modify this here!
245 class Object(object):
247 abstract class used in object reconstruction.
249 pass
252 #------------------------------------------------registertablehandler---
253 # XXX can this be made into a generic dispatch decorator???
254 def registertablehandler(table_name):
257 handlers = sys._getframe(1).f_locals['_tablehandlers']
258 def handler(func):
259 handlers[table_name] = func
260 return func
261 return handler
264 #-----------------------------------------------------------SQLReader---
265 ##!! REVISE !!##
266 ##!!! ADD FUNCTIONS AND OTHER TYPES (fallback to pickle) !!!##
267 # TODO add lazy reconstruction option for mutable and deep objects...
268 # TODO make an atomic type handler constructor... (should this be
269 # decoratable??)
270 class SQLReader(object):
273 _tablehandlers = {}
275 def __init__(self, sql):
278 self.sql = sql
279 # atomic handlers...
280 @registertablehandler('py_int')
281 def do_int(self, oid):
284 # sanity checks...
285 # XXX check if object exists (else panic?)
286 # get the object...
287 o = self.sql.select('value', 'py_int', self.sql.where(pyoid=oid)).fetchone()[0]
288 # XXX reconstruct attrs...
289 return o
290 @registertablehandler('py_long')
291 def do_long(self, oid):
294 # sanity checks...
295 # XXX check if object exists (else panic?)
296 # get the object...
297 o = self.sql.select('value', 'py_long', self.sql.where(pyoid=oid)).fetchone()[0]
298 # XXX reconstruct attrs...
299 return o
300 @registertablehandler('py_float')
301 def do_float(self, oid):
304 # sanity checks...
305 # XXX check if object exists (else panic?)
306 # get the object...
307 o = self.sql.select('value', 'py_float', self.sql.where(pyoid=oid)).fetchone()[0]
308 # XXX reconstruct attrs...
309 return o
310 ## @registertablehandler('py_complex')
311 ## def do_complex(self, oid):
312 ## '''
313 ## '''
314 ## pass
315 @registertablehandler('py_str')
316 def do_str(self, oid):
319 # sanity checks...
320 # XXX check if object exists (else panic?)
321 # get the object...
322 o = self.sql.select('value', 'py_str', self.sql.where(pyoid=oid)).fetchone()[0]
323 # XXX reconstruct attrs...
324 return o
325 @registertablehandler('py_unicode')
326 def do_unicode(self, oid):
329 # sanity checks...
330 # XXX check if object exists (else panic?)
331 # get the object...
332 o = self.sql.select('value', 'py_unicode', self.sql.where(pyoid=oid)).fetchone()[0]
333 # XXX reconstruct attrs...
334 return o
335 @registertablehandler('py_tuple')
336 def do_tuple(self, oid):
339 # sanity checks...
340 # XXX check if object exists (else panic?)
341 # get the object...
342 ##!!!
343 o = list(self.sql.select(('order', 'value'), 'py_tuple_item', self.sql.where(pyoid=oid)).fetchall())
344 o.sort()
345 o = tuple([ self.get(e) for (i, e) in o ])
346 # XXX reconstruct attrs...
347 return o
348 # mutable handlers...
349 @registertablehandler('py_list')
350 def do_list(self, oid):
353 # sanity checks...
354 # XXX check if object exists (else panic?)
355 # get the object...
356 o = list(self.sql.select(('order', 'value'), 'py_list_item', self.sql.where(pyoid=oid)).fetchall())
357 o.sort()
358 o = [ self.get(e) for (i, e) in o ]
359 # XXX reconstruct attrs...
360 return o
361 @registertablehandler('py_dict')
362 def do_dict(self, oid):
365 # sanity checks...
366 # XXX check if object exists (else panic?)
367 # get the object...
368 o = list(self.sql.select(('key', 'value'), 'py_dict_item', self.sql.where(pyoid=oid)).fetchall())
369 o = dict([ (self.get(k), self.get(v)) for (k, v) in o ])
370 # XXX reconstruct attrs...
371 return o
372 @registertablehandler('py_object')
373 def do_object(self, oid):
376 # sanity checks...
377 # XXX check if object exists (else panic?)
378 # get the object...
379 dct = dict(self.sql.select(('name', 'value'), 'py_object_attribute', self.sql.where(pyoid=oid)).fetchall())
380 # reconstruct attrs...
381 for n, v in dct.items():
382 dct[n] = self.get(v)
383 cls = dct.pop('__class__')
384 # generate the object...
385 o = Object()
386 for n, v in dct.items():
387 setattr(o, n, v)
388 o.__class__ = cls
389 return o
390 # pickle handlers...
391 @registertablehandler('py_pickled_class')
392 def do_class(self, oid):
395 # sanity checks...
396 # XXX check if object exists (else panic?)
397 # get the object...
398 o = self.sql.select('pickle', 'py_pickled_class', self.sql.where(pyoid=oid)).fetchone()[0]
399 o = pickle.loads(o)
400 # XXX reconstruct attrs...
401 return o
402 @registertablehandler('py_pickled_function')
403 def do_function(self, oid):
406 # sanity checks...
407 # XXX check if object exists (else panic?)
408 # get the object...
409 o = self.sql.select('pickle', 'py_pickled_function', self.sql.where(pyoid=oid)).fetchone()[0]
410 o = pickle.loads(o)
411 # XXX reconstruct attrs...
412 return o
413 # HL interface methods...
414 # XXX make this support the pickle protocols...
415 def get(self, oid):
418 t = self.sql.select('type', 'py_object', self.sql.where(pyoid=oid)).fetchone()[0].rstrip()
419 # NOTE: here we compensate for a sideeffect of decorating
420 # methods while the class is not there yet...
421 return self._tablehandlers[t](self, oid)
425 #-----------------------------------------------------------------------
426 #--------------------------------------------------------SQLInterface---
427 # TODO special interfaces for item access of lists and dicts...
428 # TODO special interfaces for object length and partial data (like dict
429 # keys, values... etc.)
430 # TODO compleat the types...
431 # TODO pass the transation id arround to enable:
432 # 1) query collection into one big SQL expression.
433 # 2) manage multiple transactions over one or several connections
434 # at the same time...
435 # TODO keep in mind the object id of mutable objects.
436 # NOTE: might be a good idea to track the object id in two layers:
437 # 1) save time (id in the database. unique in the db) -- sOID
438 # 2) restore time (id in runtime) -- pOID
439 # the idea is to keep a record of both ids to be able to link the
440 # stored object to its' live version.
441 # when only one id is present, it means that the object is either
442 # not yet saved or not yet read from db. if both are present,
443 # then we have both versions of the object.
445 # TODO rename this!
446 # TODO add transaction hooks and wrappers...
447 # TODO split this into an abstract sql interface and a caching
448 # sql interface...
449 class AbstractSQLInterface(object):
452 __sql_reader__ = None
453 __sql_writer__ = None
455 def __init__(self):
458 self._liveobjects = {}
459 # helpers...
460 def __update__(self, oid, obj):
462 be stupid and update.
464 overloadable.
466 WARNING: not intended for direct use.
468 ##!!!
469 return self.__sql_writer__.write(obj, oid)
470 def __insert__(self, obj):
472 be stupid and insert.
474 overloadable.
476 WARNING: not intended for direct use.
478 return self.__sql_writer__.write(obj)
479 def __select__(self, oid):
481 be stupid and get.
483 overloadable.
485 WARNING: not intended for direct use.
487 ##!!!!!!
488 return self.__sql_reader__.get(oid)
489 # interface methods
490 # XXX make this simpler!
491 def write(self, obj):
494 # 1) see if object has a sOID, if yes check if it is locked, if
495 # not then update...
496 # 2) disect and write/update...
498 pOID = id(obj)
499 tbl = dict([ (b, a) for a, b in self._liveobjects.keys() ])
500 if pOID in tbl.keys():
501 # update
502 sOID = tbl[pOID]
503 return self.__update__(sOID, obj)
504 else:
505 # write
506 sOID = self.__insert__(obj)
507 self._liveobjects[(sOID, pOID)] = obj
508 return sOID
509 # TODO add hook for live obj condition...
510 def get(self, sOID):
513 # 1) see if object is live, if yes see if it is locked or dirty (in
514 # a transaction?), if so then warn.... (???)
515 # 2) construct object.
516 # 3) save sOID, pOID, ref in self._liveobjects
518 tbl = dict(self._liveobjects.keys())
519 if sOID in tbl.keys():
520 ##!!! add hook...
521 ## print 'WARNING: object already open.'
522 return self._liveobjects[(sOID, tbl[sOID])]
523 return self.__select__(sOID)
524 ##!!!
525 def delete(self, sOID):
528 raise NotImplementedError
532 #=======================================================================
533 # vim:set ts=4 sw=4 nowrap :