1 #=======================================================================
3 __version__
= '''0.0.01'''
4 __sub_version__
= '''20050824030618'''
5 __copyright__
= '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
16 #-----------------------------------------------------------------------
17 #-------------------------------------------------------transactioned---
18 # XXX transaction decorator...
26 #-----------------------------------------------------------------------
27 #-------------------------------------------------registertypehandler---
28 # XXX can this be made into a generic dispatch decorator???
29 def registertypehandler(type):
32 handlers
= sys
._getframe
(1).f_locals
['_typehandlers']
39 #-----------------------------------------------------------SQLWriter---
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
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
49 class SQLWriter(object):
54 __object_native_attrs__
= ('__dict__', '__class__')
56 def __init__(self
, sql
):
60 # atomic type handlers...
61 @registertypehandler(int)
62 def do_int(self
, o
, oid
=None):
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
)
69 @registertypehandler(long)
70 def do_long(self
, o
, oid
=None):
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
)
77 @registertypehandler(float)
78 def do_float(self
, o
, oid
=None):
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
)
85 ## @registertypehandler(complex)
86 ## def do_complex(self, o, oid=None):
90 @registertypehandler(str)
91 def do_str(self
, o
, oid
=None):
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
)
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
)
106 @registertypehandler(tuple)
107 def do_tuple(self
, tpl
, oid
=None):
111 pyoid(oid) -> py_object.pyoid
113 py_tuple_items row format:
114 pyoid(oid) -> py_tuple.pyoid
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
)
126 # mutable handlers...
127 @registertypehandler(list)
128 def do_list(self
, lst
, oid
=None):
132 pyoid(oid) -> py_object.pyoid
134 py_list_items row format:
135 pyoid(oid) -> py_list.pyoid
137 value(oid) -> py_object.pyoid
139 NOTE: if object id (oid) is given, the object will be updated.
142 # XXX use the strategy of "keep the existing, add the new,
144 self
.sql
.delete('py_list_item', self
.sql
.where(pyoid
=oid
))
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
)
155 @registertypehandler(dict)
156 def do_dict(self
, dct
, oid
=None):
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.
170 # XXX use the strategy of "keep the existing, add the new,
172 self
.sql
.delete('py_dict_item', self
.sql
.where(pyoid
=oid
))
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
)
184 @registertypehandler(object)
185 def do_object(self
, obj
, oid
=None):
188 NOTE: if object id (oid) is given, the object will be updated.
191 # XXX use the strategy of "keep the existing, add the new,
193 self
.sql
.delete('py_object_attribute', self
.sql
.where(pyoid
=oid
))
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
)
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',
216 pickle
=pickle
.dumps(cls
))
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',
227 pickle
=pickle
.dumps(cls
))
229 # HL interface methods...
230 # XXX make this support the pickle protocols...
231 def write(self
, obj
, oid
=None):
235 handler
= self
._typehandlers
.get(t
, 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.
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']
259 handlers
[table_name
] = func
264 #-----------------------------------------------------------SQLReader---
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
270 class SQLReader(object):
275 def __init__(self
, sql
):
280 @registertablehandler('py_int')
281 def do_int(self
, oid
):
285 # XXX check if object exists (else panic?)
287 o
= self
.sql
.select('value', 'py_int', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
288 # XXX reconstruct attrs...
290 @registertablehandler('py_long')
291 def do_long(self
, oid
):
295 # XXX check if object exists (else panic?)
297 o
= self
.sql
.select('value', 'py_long', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
298 # XXX reconstruct attrs...
300 @registertablehandler('py_float')
301 def do_float(self
, oid
):
305 # XXX check if object exists (else panic?)
307 o
= self
.sql
.select('value', 'py_float', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
308 # XXX reconstruct attrs...
310 ## @registertablehandler('py_complex')
311 ## def do_complex(self, oid):
315 @registertablehandler('py_str')
316 def do_str(self
, oid
):
320 # XXX check if object exists (else panic?)
322 o
= self
.sql
.select('value', 'py_str', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
323 # XXX reconstruct attrs...
325 @registertablehandler('py_unicode')
326 def do_unicode(self
, oid
):
330 # XXX check if object exists (else panic?)
332 o
= self
.sql
.select('value', 'py_unicode', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
333 # XXX reconstruct attrs...
335 @registertablehandler('py_tuple')
336 def do_tuple(self
, oid
):
340 # XXX check if object exists (else panic?)
343 o
= list(self
.sql
.select(('order', 'value'), 'py_tuple_item', self
.sql
.where(pyoid
=oid
)).fetchall())
345 o
= tuple([ self
.get(e
) for (i
, e
) in o
])
346 # XXX reconstruct attrs...
348 # mutable handlers...
349 @registertablehandler('py_list')
350 def do_list(self
, oid
):
354 # XXX check if object exists (else panic?)
356 o
= list(self
.sql
.select(('order', 'value'), 'py_list_item', self
.sql
.where(pyoid
=oid
)).fetchall())
358 o
= [ self
.get(e
) for (i
, e
) in o
]
359 # XXX reconstruct attrs...
361 @registertablehandler('py_dict')
362 def do_dict(self
, oid
):
366 # XXX check if object exists (else panic?)
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...
372 @registertablehandler('py_object')
373 def do_object(self
, oid
):
377 # XXX check if object exists (else panic?)
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():
383 cls
= dct
.pop('__class__')
384 # generate the object...
386 for n
, v
in dct
.items():
391 @registertablehandler('py_pickled_class')
392 def do_class(self
, oid
):
396 # XXX check if object exists (else panic?)
398 o
= self
.sql
.select('pickle', 'py_pickled_class', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
400 # XXX reconstruct attrs...
402 @registertablehandler('py_pickled_function')
403 def do_function(self
, oid
):
407 # XXX check if object exists (else panic?)
409 o
= self
.sql
.select('pickle', 'py_pickled_function', self
.sql
.where(pyoid
=oid
)).fetchone()[0]
411 # XXX reconstruct attrs...
413 # HL interface methods...
414 # XXX make this support the pickle protocols...
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.
446 # TODO add transaction hooks and wrappers...
447 # TODO split this into an abstract sql interface and a caching
449 class AbstractSQLInterface(object):
452 __sql_reader__
= None
453 __sql_writer__
= None
458 self
._liveobjects
= {}
460 def __update__(self
, oid
, obj
):
462 be stupid and update.
466 WARNING: not intended for direct use.
469 return self
.__sql
_writer
__.write(obj
, oid
)
470 def __insert__(self
, obj
):
472 be stupid and insert.
476 WARNING: not intended for direct use.
478 return self
.__sql
_writer
__.write(obj
)
479 def __select__(self
, oid
):
485 WARNING: not intended for direct use.
488 return self
.__sql
_reader
__.get(oid
)
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
496 # 2) disect and write/update...
499 tbl
= dict([ (b
, a
) for a
, b
in self
._liveobjects
.keys() ])
500 if pOID
in tbl
.keys():
503 return self
.__update
__(sOID
, obj
)
506 sOID
= self
.__insert
__(obj
)
507 self
._liveobjects
[(sOID
, pOID
)] = obj
509 # TODO add hook for live obj condition...
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():
521 ## print 'WARNING: object already open.'
522 return self
._liveobjects
[(sOID
, tbl
[sOID
])]
523 return self
.__select
__(sOID
)
525 def delete(self
, sOID
):
528 raise NotImplementedError
532 #=======================================================================
533 # vim:set ts=4 sw=4 nowrap :