*** empty log message ***
[pli.git] / pli / persistance / sql / core.py
blob8232458119f6631501f304d589f92520854b701c
1 #=======================================================================
3 __version__ = '''0.0.05'''
4 __sub_version__ = '''20070105020543'''
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 # XXX do update...
49 # return old id...)
50 class SQLWriter(object):
51 '''
52 '''
53 _typehandlers = {}
55 __object_native_attrs__ = ('__dict__', '__class__')
57 def __init__(self, sql):
58 '''
59 '''
60 self.sql = sql
61 # atomic type handlers...
62 @registertypehandler(int)
63 def do_int(self, o, oid=None):
64 '''
65 '''
66 obj_id = self.sql.insert('py_object', type='py_int').lastrowid
67 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
68 self.sql.insert('py_int', pyoid=obj_id, value=o)
69 return obj_id
70 @registertypehandler(long)
71 def do_long(self, o, oid=None):
72 '''
73 '''
74 obj_id = self.sql.insert('py_object', type='py_long').lastrowid
75 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
76 self.sql.insert('py_long', pyoid=obj_id, value=o)
77 return obj_id
78 @registertypehandler(float)
79 def do_float(self, o, oid=None):
80 '''
81 '''
82 obj_id = self.sql.insert('py_object', type='py_float').lastrowid
83 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
84 self.sql.insert('py_float', pyoid=obj_id, value=o)
85 return obj_id
86 ## @registertypehandler(complex)
87 ## def do_complex(self, o, oid=None):
88 ## '''
89 ## '''
90 ## pass
91 @registertypehandler(str)
92 def do_str(self, o, oid=None):
93 '''
94 '''
95 obj_id = self.sql.insert('py_object', type='py_str').lastrowid
96 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
97 self.sql.insert('py_str', pyoid=obj_id, value=o)
98 return obj_id
99 @registertypehandler(unicode)
100 def do_unicode(self, o, oid=None):
103 obj_id = self.sql.insert('py_object', type='py_unicode').lastrowid
104 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
105 self.sql.insert('py_unicode', pyoid=obj_id, value=o)
106 return obj_id
107 @registertypehandler(tuple)
108 def do_tuple(self, tpl, oid=None):
111 py_tuple row format:
112 pyoid(oid) -> py_object.pyoid
114 py_tuple_items row format:
115 pyoid(oid) -> py_tuple.pyoid
116 order(int)
117 value(oid) -> py_object.pyoid
119 obj_id = self.sql.insert('py_object', type='py_tuple').lastrowid
120 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
121 self.sql.insert('py_tuple', pyoid=obj_id)
122 # XXX this might be bad as it will not track the HL object OIDs
123 # and thus might split some mutable objects in two or more
124 # independent versions...
125 for i, o in enumerate(tpl):
126 # insert the element...
127 item_id = self.write(o)
128 self.sql.insert('py_tuple_item', order=i, pyoid=obj_id, value=item_id)
129 return obj_id
130 # mutable handlers...
131 @registertypehandler(list)
132 def do_list(self, lst, oid=None):
135 py_list row format:
136 pyoid(oid) -> py_object.pyoid
138 py_list_items row format:
139 pyoid(oid) -> py_list.pyoid
140 order(int)
141 value(oid) -> py_object.pyoid
143 NOTE: if object id (oid) is given, the object will be updated.
145 if oid != None:
146 # XXX use the strategy of "keep the existing, add the new,
147 # remove the old."
148 self.sql.delete('py_list_item', self.sql.where(pyoid=oid))
149 obj_id = oid
150 else:
151 obj_id = self.sql.insert('py_object', type='py_list').lastrowid
152 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
153 self.sql.insert('py_list', pyoid=obj_id)
154 # insert the list items...
155 # XXX this might be bad as it will not track the HL object OIDs
156 # and thus might split some mutable objects in two or more
157 # independent versions...
158 for i, o in enumerate(lst):
159 item_id = self.write(o)
160 self.sql.insert('py_list_item', order=i, pyoid=obj_id, value=item_id)
161 return obj_id
162 @registertypehandler(dict)
163 def do_dict(self, dct, oid=None):
166 py_dict row format:
167 pyoid(oid) -> py_object.pyoid
169 py_dict_items row format:
170 pyoid(oid) -> py_dict.pyoid
171 key(oid) -> py_object.pyoid
172 value(oid) -> py_object.pyoid
174 NOTE: if object id (oid) is given, the object will be updated.
176 if oid != None:
177 # XXX use the strategy of "keep the existing, add the new,
178 # remove the old."
179 self.sql.delete('py_dict_item', self.sql.where(pyoid=oid))
180 obj_id = oid
181 else:
182 obj_id = self.sql.insert('py_object', type='py_dict').lastrowid
183 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
184 self.sql.insert('py_dict', pyoid=obj_id)
185 # insert the items...
186 # XXX this might be bad as it will not track the HL object OIDs
187 # and thus might split some mutable objects in two or more
188 # independent versions...
189 for k, v in dct.items():
190 key_id = self.write(k)
191 val_id = self.write(v)
192 self.sql.insert('py_dict_item', pyoid=obj_id, key=key_id, value=val_id)
193 return obj_id
194 @registertypehandler(object)
195 def do_object(self, obj, oid=None):
198 NOTE: if object id (oid) is given, the object will be updated.
200 if oid != None:
201 # XXX use the strategy of "keep the existing, add the new,
202 # remove the old."
203 self.sql.delete('py_object_attribute', self.sql.where(pyoid=oid))
204 obj_id = oid
205 else:
206 obj_id = self.sql.insert('py_object').lastrowid
207 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
209 for n in self.__object_native_attrs__:
210 # insert the element...
211 ## name_id = self.write(n)
212 ## self.sql.insert('py_object_attribute', pyoid=obj_id, name=name_id, value=val_id)
213 # the if here is to avoid special cases like None and Guido
214 # knows what that may not have a special attr like __dict__
215 # in the case of None.... #$%^&*
216 if hasattr(obj, n):
217 val_id = self.write(getattr(obj, n))
218 self.sql.insert('py_object_attribute', pyoid=obj_id, name=n, value=val_id)
219 return obj_id
220 # pickle handlers...
221 @registertypehandler(type)
222 def do_class(self, cls, oid=None):
225 obj_id = self.sql.insert('py_object', type='py_pickled_class').lastrowid
226 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
228 # for some reason pickle can't pickle NoneType....
229 if cls == types.NoneType:
230 cls = None
232 self.sql.insert('py_pickled_class',
233 pyoid=obj_id,
234 pickle=pickle.dumps(cls))
235 return obj_id
236 @registertypehandler(types.FunctionType)
237 def do_function(self, cls, oid=None):
240 obj_id = self.sql.insert('py_object', type='py_pickled_function').lastrowid
241 obj_id = self.sql.select('pyoid', 'py_object', self.sql.where(oid=obj_id)).fetchone()[0]
243 self.sql.insert('py_pickled_function',
244 pyoid=obj_id,
245 pickle=pickle.dumps(cls))
246 return obj_id
247 # HL interface methods...
248 # XXX make this support the pickle protocols...
249 ##!!! REVISE
250 def write(self, obj, oid=None):
253 t = type(obj)
254 handler = self._typehandlers.get(t, None)
255 if handler is None:
256 return self.do_object(obj, oid)
257 return handler(self, obj, oid)
258 ##!!! REVISE
259 def writebyname(self, name, obj):
262 oid = self.write(obj)
263 obj_id = self.sql.insert('py_registry', name=name, pyoid=oid)
264 return oid
268 #-----------------------------------------------------------------------
269 #--------------------------------------------------------------Object---
270 # WARNING: do not modify this here!
271 class Object(object):
273 abstract class used in object reconstruction.
275 pass
278 #------------------------------------------------registertablehandler---
279 # XXX can this be made into a generic dispatch decorator???
280 def registertablehandler(table_name):
283 handlers = sys._getframe(1).f_locals['_tablehandlers']
284 def handler(func):
285 handlers[table_name] = func
286 return func
287 return handler
290 #-----------------------------------------------------------SQLReader---
291 ##!! REVISE !!##
292 ##!!! ADD FUNCTIONS AND OTHER TYPES (fallback to pickle) !!!##
293 # TODO add lazy reconstruction option for mutable and deep objects...
294 # TODO make an atomic type handler constructor... (should this be
295 # decoratable??)
296 # TODO make the table names configurable....
297 class SQLReader(object):
300 _tablehandlers = {}
302 def __init__(self, sql):
305 self.sql = sql
306 # atomic handlers...
307 @registertablehandler('py_int')
308 def do_int(self, oid):
311 # sanity checks...
312 # XXX check if object exists (else panic?)
313 # get the object...
314 o = self.sql.select('value', 'py_int', self.sql.where(pyoid=oid)).fetchone()[0]
315 # XXX reconstruct attrs...
316 return o
317 @registertablehandler('py_long')
318 def do_long(self, oid):
321 # sanity checks...
322 # XXX check if object exists (else panic?)
323 # get the object...
324 o = self.sql.select('value', 'py_long', self.sql.where(pyoid=oid)).fetchone()[0]
325 # XXX reconstruct attrs...
326 return o
327 @registertablehandler('py_float')
328 def do_float(self, oid):
331 # sanity checks...
332 # XXX check if object exists (else panic?)
333 # get the object...
334 o = self.sql.select('value', 'py_float', self.sql.where(pyoid=oid)).fetchone()[0]
335 # XXX reconstruct attrs...
336 return o
337 ## @registertablehandler('py_complex')
338 ## def do_complex(self, oid):
339 ## '''
340 ## '''
341 ## pass
342 @registertablehandler('py_str')
343 def do_str(self, oid):
346 # sanity checks...
347 # XXX check if object exists (else panic?)
348 # get the object...
349 o = self.sql.select('value', 'py_str', self.sql.where(pyoid=oid)).fetchone()[0]
350 # XXX reconstruct attrs...
351 return o
352 @registertablehandler('py_unicode')
353 def do_unicode(self, oid):
356 # sanity checks...
357 # XXX check if object exists (else panic?)
358 # get the object...
359 o = self.sql.select('value', 'py_unicode', self.sql.where(pyoid=oid)).fetchone()[0]
360 # XXX reconstruct attrs...
361 return o
362 @registertablehandler('py_tuple')
363 def do_tuple(self, oid):
366 # sanity checks...
367 # XXX check if object exists (else panic?)
368 # get the object...
369 ##!!!
370 o = list(self.sql.select(('order', 'value'), 'py_tuple_item', self.sql.where(pyoid=oid)).fetchall())
371 o.sort()
372 o = tuple([ self.get(e) for (i, e) in o ])
373 # XXX reconstruct attrs...
374 return o
375 # mutable handlers...
376 @registertablehandler('py_list')
377 def do_list(self, oid):
380 # sanity checks...
381 # XXX check if object exists (else panic?)
382 # get the object...
383 o = list(self.sql.select(('order', 'value'), 'py_list_item', self.sql.where(pyoid=oid)).fetchall())
384 o.sort()
385 o = [ self.get(e) for (i, e) in o ]
386 # XXX reconstruct attrs...
387 return o
388 @registertablehandler('py_dict')
389 def do_dict(self, oid):
392 # sanity checks...
393 # XXX check if object exists (else panic?)
394 # get the object...
395 o = list(self.sql.select(('key', 'value'), 'py_dict_item', self.sql.where(pyoid=oid)).fetchall())
396 o = dict([ (self.get(k), self.get(v)) for (k, v) in o ])
397 # XXX reconstruct attrs...
398 return o
399 @registertablehandler('py_object')
400 def do_object(self, oid):
403 # sanity checks...
404 # XXX check if object exists (else panic?)
405 # get the object...
406 dct = dict(self.sql.select(('name', 'value'), 'py_object_attribute', self.sql.where(pyoid=oid)).fetchall())
407 # reconstruct attrs...
408 for n, v in dct.items():
409 dct[n] = self.get(v)
410 cls = dct.pop('__class__')
411 # there is only one None object and it is already created...
412 if cls in (types.NoneType, None):
413 return None
414 # generate the object...
415 o = Object()
416 for n, v in dct.items():
417 setattr(o, n, v)
418 o.__class__ = cls
419 return o
420 # pickle handlers...
421 @registertablehandler('py_pickled_class')
422 def do_class(self, oid):
425 # sanity checks...
426 # XXX check if object exists (else panic?)
427 # get the object...
428 o = self.sql.select('pickle', 'py_pickled_class', self.sql.where(pyoid=oid)).fetchone()[0]
429 o = pickle.loads(o)
430 # NoneType is represented as None...
431 if o == None:
432 return types.NoneType
433 # XXX reconstruct attrs...
434 return o
435 @registertablehandler('py_pickled_function')
436 def do_function(self, oid):
439 # sanity checks...
440 # XXX check if object exists (else panic?)
441 # get the object...
442 o = self.sql.select('pickle', 'py_pickled_function', self.sql.where(pyoid=oid)).fetchone()[0]
443 o = pickle.loads(o)
444 # XXX reconstruct attrs...
445 return o
446 # HL interface methods...
447 def get_oid(self, name):
450 try:
451 return self.sql.select('pyoid', 'py_registry', self.sql.where(name=name)).fetchone()[0]
452 except:
453 return None
455 # XXX make this support the pickle protocols...
456 def get(self, oid):
459 if type(oid) is str:
460 oid = self.sql.select('pyoid', self.sql.where(name=oid)).fetchone()[0].rstrip()
461 t = self.sql.select('type', 'py_object', self.sql.where(pyoid=oid)).fetchone()[0].rstrip()
462 # NOTE: here we compensate for a sideeffect of decorating
463 # methods while the class is not there yet...
464 return self._tablehandlers[t](self, oid)
468 #-----------------------------------------------------------------------
469 #--------------------------------------------------------SQLInterface---
470 # TODO special interfaces for item access of lists and dicts...
471 # TODO special interfaces for object length and partial data (like dict
472 # keys, values... etc.)
473 # TODO compleat the types...
474 # TODO pass the transation id arround to enable:
475 # 1) query collection into one big SQL expression.
476 # 2) manage multiple transactions over one or several connections
477 # at the same time...
478 # TODO keep in mind the object id of mutable objects.
479 # NOTE: might be a good idea to track the object id in two layers:
480 # 1) save time (id in the database. unique in the db) -- sOID
481 # 2) restore time (id in runtime) -- pOID
482 # the idea is to keep a record of both ids to be able to link the
483 # stored object to its' live version.
484 # when only one id is present, it means that the object is either
485 # not yet saved or not yet read from db. if both are present,
486 # then we have both versions of the object.
488 # TODO rename this!
489 # TODO add transaction hooks and wrappers...
490 # TODO split this into an abstract sql interface and a caching
491 # sql interface...
492 class AbstractSQLInterface(object):
495 __sql_reader__ = None
496 __sql_writer__ = None
498 def __init__(self):
501 self._liveobjects = {}
502 # helpers...
503 def __update__(self, oid, obj):
505 be stupid and update.
507 overloadable.
509 WARNING: not intended for direct use.
511 ##!!!
512 return self.__sql_writer__.write(obj, oid)
513 def __insert__(self, a, b=None):
515 be stupid and insert.
517 overloadable.
519 WARNING: not intended for direct use.
521 if b == None:
522 return self.__sql_writer__.write(a)
523 ##!!!
524 return self.__sql_writer__.writebyname(a, b)
525 def __select__(self, oid):
527 be stupid and get.
529 overloadable.
531 WARNING: not intended for direct use.
533 ##!!!!!!
534 return self.__sql_reader__.get(oid)
535 def __name2oid__(self, name):
538 return self.__sql_reader__.get_oid(name)
539 # interface methods
540 # XXX make this simpler!
541 def write(self, a, b=None):
544 this can be one of:
545 write(obj) -> OID
546 write(name, obj) -> OID
548 NOTE: in case #2 obj can not be None.
550 if b == None:
551 obj = a
552 name = b
553 else:
554 obj = b
555 name = a
556 # 1) see if object has a sOID, if yes check if it is locked, if
557 # not then update...
558 # 2) disect and write/update...
560 pOID = id(obj)
561 tbl = dict([ (b, a) for a, b in self._liveobjects.keys() ])
562 if pOID in tbl.keys():
563 # update
564 sOID = tbl[pOID]
565 return self.__update__(sOID, obj)
566 else:
567 # write
568 if name != None:
569 # insert name to registry...
570 sOID = self.__insert__(name, obj)
571 else:
572 sOID = self.__insert__(obj)
573 self._liveobjects[(sOID, pOID)] = obj
574 return sOID
575 # TODO add hook for live obj condition...
576 def get(self, sOID):
579 # 1) see if object is live, if yes see if it is locked or dirty (in
580 # a transaction?), if so then warn.... (???)
581 # 2) construct object.
582 # 3) save sOID, pOID, ref in self._liveobjects
584 name = None
585 # get object id by name...
586 if not self.isoid(sOID):
587 name = sOID
588 sOID = self.__name2oid__(sOID)
589 if sOID == None:
590 raise KeyError, 'object "%s" does not exist in DB.'
591 # get the object...
592 tbl = dict(self._liveobjects.keys())
593 if sOID in tbl.keys():
594 ##!!! add hook...
595 ## print 'WARNING: object already open.'
596 return self._liveobjects[(sOID, tbl[sOID])]
597 obj = self.__select__(sOID)
598 self._liveobjects[(sOID, id(obj))] = obj
599 return obj
600 ##!!!
601 def delete(self, sOID):
604 raise NotImplementedError
605 # registry specific methods...
606 def isoid(self, o):
609 return type(o) in (int, long)
613 if __name__ == '__main__':
615 import psycopg2 as psycopg
616 import sql
618 DBHOST = 'localhost'
619 ## DBHOST = 'mozg.cis.bigur.ru'
620 DBDATABASE = 'poker'
621 DBUSER = 'f_lynx'
622 PASSWORD = '1234567'
624 dbcon = psycopg.connect('host=%s dbname=%s user=%s password=%s' \
625 % (DBHOST, DBDATABASE, DBUSER, PASSWORD))
627 sqlobj = sql.SQL(dbcon)
629 sqlinterface = AbstractSQLInterface()
631 sqlinterface.__sql_reader__ = SQLReader(sqlobj)
632 sqlinterface.__sql_writer__ = SQLWriter(sqlobj)
635 i = 2985
636 d = sqlinterface.get(i)
638 ## d = {1:1,2:2,3:3}
639 ## #sqlinterface.write('aaaa', d)
640 ## i = sqlinterface.write(d)
641 ## sqlinterface.__sql_reader__.sql.connection.commit()
643 print '>>>', i, sqlinterface.get(i)
645 d['xxx'] = 'xxx'
646 d['yyy'] = 'yyy'
648 i = sqlinterface.write(d)
649 sqlinterface.__sql_reader__.sql.connection.commit()
651 print '>>>', i, sqlinterface.get(i)
653 sqlinterface.__sql_reader__.sql.connection.commit()
658 #=======================================================================
659 # vim:set ts=4 sw=4 nowrap :