comparison roundup/backends/rdbms_common.py @ 2098:18addf2a8596

Implemented proper datatypes in mysql and postgresql backends... ...(well, sqlite too, but that doesn't care). Probably should use BOOLEAN instead of INTEGER for the Boolean props. Need to fix a bizzaro MySQL error (gee, how unusual) Need to finish MySQL migration from "version 1" database schemas.
author Richard Jones <richard@users.sourceforge.net>
date Mon, 22 Mar 2004 07:45:40 +0000
parents 3f6024ab2c7a
children 62ed6505cbec
comparison
equal deleted inserted replaced
2097:37ede7c5f5c5 2098:18addf2a8596
1 # $Id: rdbms_common.py,v 1.83 2004-03-21 23:39:08 richard Exp $ 1 # $Id: rdbms_common.py,v 1.84 2004-03-22 07:45:39 richard Exp $
2 ''' Relational database (SQL) backend common code. 2 ''' Relational database (SQL) backend common code.
3 3
4 Basics: 4 Basics:
5 5
6 - map roundup classes to relational tables 6 - map roundup classes to relational tables
43 from sessions_rdbms import Sessions, OneTimeKeys 43 from sessions_rdbms import Sessions, OneTimeKeys
44 from roundup.date import Range 44 from roundup.date import Range
45 45
46 # number of rows to keep in memory 46 # number of rows to keep in memory
47 ROW_CACHE_SIZE = 100 47 ROW_CACHE_SIZE = 100
48
49 def _num_cvt(num):
50 num = str(num)
51 try:
52 return int(num)
53 except:
54 return float(num)
48 55
49 class Database(FileStorage, hyperdb.Database, roundupdb.Database): 56 class Database(FileStorage, hyperdb.Database, roundupdb.Database):
50 ''' Wrapper around an SQL database that presents a hyperdb interface. 57 ''' Wrapper around an SQL database that presents a hyperdb interface.
51 58
52 - some functionality is specific to the actual SQL database, hence 59 - some functionality is specific to the actual SQL database, hence
210 for klass in self.classes.values(): 217 for klass in self.classes.values():
211 for nodeid in klass.list(): 218 for nodeid in klass.list():
212 klass.index(nodeid) 219 klass.index(nodeid)
213 self.indexer.save_index() 220 self.indexer.save_index()
214 221
222
223 hyperdb_to_sql_datatypes = {
224 hyperdb.String : 'VARCHAR(255)',
225 hyperdb.Date : 'TIMESTAMP',
226 hyperdb.Link : 'INTEGER',
227 hyperdb.Interval : 'VARCHAR(255)',
228 hyperdb.Password : 'VARCHAR(255)',
229 hyperdb.Boolean : 'INTEGER',
230 hyperdb.Number : 'REAL',
231 }
215 def determine_columns(self, properties): 232 def determine_columns(self, properties):
216 ''' Figure the column names and multilink properties from the spec 233 ''' Figure the column names and multilink properties from the spec
217 234
218 "properties" is a list of (name, prop) where prop may be an 235 "properties" is a list of (name, prop) where prop may be an
219 instance of a hyperdb "type" _or_ a string repr of that type. 236 instance of a hyperdb "type" _or_ a string repr of that type.
220 ''' 237 '''
221 cols = ['_actor', '_activity', '_creator', '_creation'] 238 cols = [
239 ('_actor', 'INTEGER'),
240 ('_activity', 'DATE'),
241 ('_creator', 'INTEGER'),
242 ('_creation', 'DATE')
243 ]
222 mls = [] 244 mls = []
223 # add the multilinks separately 245 # add the multilinks separately
224 for col, prop in properties: 246 for col, prop in properties:
225 if isinstance(prop, Multilink): 247 if isinstance(prop, Multilink):
226 mls.append(col) 248 mls.append(col)
227 elif isinstance(prop, type('')) and prop.find('Multilink') != -1: 249 continue
228 mls.append(col) 250
229 else: 251 if isinstance(prop, type('')):
230 cols.append('_'+col) 252 raise ValueError, "string property spec!"
253 #and prop.find('Multilink') != -1:
254 #mls.append(col)
255
256 datatype = self.hyperdb_to_sql_datatypes[prop.__class__]
257 cols.append(('_'+col, datatype))
258
231 cols.sort() 259 cols.sort()
232 return cols, mls 260 return cols, mls
233 261
234 def update_class(self, spec, old_spec, force=0): 262 def update_class(self, spec, old_spec, force=0):
235 ''' Determine the differences between the current spec and the 263 ''' Determine the differences between the current spec and the
313 ''' create the class table for the given spec 341 ''' create the class table for the given spec
314 ''' 342 '''
315 cols, mls = self.determine_columns(spec.properties.items()) 343 cols, mls = self.determine_columns(spec.properties.items())
316 344
317 # add on our special columns 345 # add on our special columns
318 cols.append('id') 346 cols.append(('id', 'INTEGER PRIMARY KEY'))
319 cols.append('__retired__') 347 cols.append(('__retired__', 'INTEGER DEFAULT 0'))
320 348
321 # create the base table 349 # create the base table
322 scols = ','.join(['%s varchar'%x for x in cols]) 350 scols = ','.join(['%s %s'%x for x in cols])
323 sql = 'create table _%s (%s)'%(spec.classname, scols) 351 sql = 'create table _%s (%s)'%(spec.classname, scols)
324 if __debug__: 352 if __debug__:
325 print >>hyperdb.DEBUG, 'create_class', (self, sql) 353 print >>hyperdb.DEBUG, 'create_class', (self, sql)
326 self.cursor.execute(sql) 354 self.cursor.execute(sql)
327 355
330 return cols, mls 358 return cols, mls
331 359
332 def create_class_table_indexes(self, spec): 360 def create_class_table_indexes(self, spec):
333 ''' create the class table for the given spec 361 ''' create the class table for the given spec
334 ''' 362 '''
335 # create id index
336 index_sql1 = 'create index _%s_id_idx on _%s(id)'%(
337 spec.classname, spec.classname)
338 if __debug__:
339 print >>hyperdb.DEBUG, 'create_index', (self, index_sql1)
340 self.cursor.execute(index_sql1)
341
342 # create __retired__ index 363 # create __retired__ index
343 index_sql2 = 'create index _%s_retired_idx on _%s(__retired__)'%( 364 index_sql2 = 'create index _%s_retired_idx on _%s(__retired__)'%(
344 spec.classname, spec.classname) 365 spec.classname, spec.classname)
345 if __debug__: 366 if __debug__:
346 print >>hyperdb.DEBUG, 'create_index', (self, index_sql2) 367 print >>hyperdb.DEBUG, 'create_index', (self, index_sql2)
374 self.cursor.execute(index_sql) 395 self.cursor.execute(index_sql)
375 396
376 def create_class_table_key_index(self, cn, key): 397 def create_class_table_key_index(self, cn, key):
377 ''' create the class table for the given spec 398 ''' create the class table for the given spec
378 ''' 399 '''
379 if __debug__: 400 sql = 'create index _%s_%s_idx on _%s(_%s)'%(cn, key, cn, key)
380 print >>hyperdb.DEBUG, 'update_class setting keyprop %r'% \ 401 if __debug__:
381 key 402 print >>hyperdb.DEBUG, 'create_class_tab_key_index', (self, sql)
382 index_sql3 = 'create index _%s_%s_idx on _%s(_%s)'%(cn, key, 403 self.cursor.execute(sql)
383 cn, key)
384 if __debug__:
385 print >>hyperdb.DEBUG, 'create_index', (self, index_sql3)
386 self.cursor.execute(index_sql3)
387 404
388 def drop_class_table_key_index(self, cn, key): 405 def drop_class_table_key_index(self, cn, key):
389 table_name = '_%s'%cn 406 table_name = '_%s'%cn
390 index_name = '_%s_%s_idx'%(cn, key) 407 index_name = '_%s_%s_idx'%(cn, key)
391 if not self.sql_index_exists(table_name, index_name): 408 if not self.sql_index_exists(table_name, index_name):
392 return 409 return
393 sql = 'drop index '+index_name 410 sql = 'drop index '+index_name
394 if __debug__: 411 if __debug__:
395 print >>hyperdb.DEBUG, 'drop_index', (self, sql) 412 print >>hyperdb.DEBUG, 'drop_class_tab_key_index', (self, sql)
396 self.cursor.execute(sql) 413 self.cursor.execute(sql)
397 414
398 def create_journal_table(self, spec): 415 def create_journal_table(self, spec):
399 ''' create the journal table for a class given the spec and 416 ''' create the journal table for a class given the spec and
400 already-determined cols 417 already-determined cols
401 ''' 418 '''
402 # journal table 419 # journal table
403 cols = ','.join(['%s varchar'%x 420 cols = ','.join(['%s varchar'%x
404 for x in 'nodeid date tag action params'.split()]) 421 for x in 'nodeid date tag action params'.split()])
405 sql = 'create table %s__journal (%s)'%(spec.classname, cols) 422 sql = '''create table %s__journal (
406 if __debug__: 423 nodeid integer, date timestamp, tag varchar(255),
407 print >>hyperdb.DEBUG, 'create_class', (self, sql) 424 action varchar(255), params varchar(25))'''%spec.classname
425 if __debug__:
426 print >>hyperdb.DEBUG, 'create_journal_table', (self, sql)
408 self.cursor.execute(sql) 427 self.cursor.execute(sql)
409 self.create_journal_table_indexes(spec) 428 self.create_journal_table_indexes(spec)
410 429
411 def create_journal_table_indexes(self, spec): 430 def create_journal_table_indexes(self, spec):
412 # index on nodeid 431 # index on nodeid
474 493
475 # now create the multilink tables 494 # now create the multilink tables
476 for ml in mls: 495 for ml in mls:
477 self.create_multilink_table(spec, ml) 496 self.create_multilink_table(spec, ml)
478 497
479 # ID counter
480 sql = 'insert into ids (name, num) values (%s,%s)'%(self.arg, self.arg)
481 vals = (spec.classname, 1)
482 if __debug__:
483 print >>hyperdb.DEBUG, 'create_class', (self, sql, vals)
484 self.cursor.execute(sql, vals)
485
486 def drop_class(self, cn, spec): 498 def drop_class(self, cn, spec):
487 ''' Drop the given table from the database. 499 ''' Drop the given table from the database.
488 500
489 Drop the journal and multilink tables too. 501 Drop the journal and multilink tables too.
490 ''' 502 '''
495 if isinstance(prop, Multilink): 507 if isinstance(prop, Multilink):
496 mls.append(propname) 508 mls.append(propname)
497 509
498 # drop class table and indexes 510 # drop class table and indexes
499 self.drop_class_table_indexes(cn, spec[0]) 511 self.drop_class_table_indexes(cn, spec[0])
500 sql = 'drop table _%s'%cn 512
501 if __debug__: 513 self.drop_class_table(cn)
502 print >>hyperdb.DEBUG, 'drop_class', (self, sql)
503 self.cursor.execute(sql)
504 514
505 # drop journal table and indexes 515 # drop journal table and indexes
506 self.drop_journal_table_indexes(cn) 516 self.drop_journal_table_indexes(cn)
507 sql = 'drop table %s__journal'%cn 517 sql = 'drop table %s__journal'%cn
508 if __debug__: 518 if __debug__:
515 sql = 'drop table %s_%s'%(spec.classname, ml) 525 sql = 'drop table %s_%s'%(spec.classname, ml)
516 if __debug__: 526 if __debug__:
517 print >>hyperdb.DEBUG, 'drop_class', (self, sql) 527 print >>hyperdb.DEBUG, 'drop_class', (self, sql)
518 self.cursor.execute(sql) 528 self.cursor.execute(sql)
519 529
530 def drop_class_table(self, cn):
531 sql = 'drop table _%s'%cn
532 if __debug__:
533 print >>hyperdb.DEBUG, 'drop_class', (self, sql)
534 self.cursor.execute(sql)
535
520 # 536 #
521 # Classes 537 # Classes
522 # 538 #
523 def __getattr__(self, classname): 539 def __getattr__(self, classname):
524 ''' A convenient way of calling self.getclass(classname). 540 ''' A convenient way of calling self.getclass(classname).
579 if __debug__: 595 if __debug__:
580 print >>hyperdb.DEBUG, 'clear', (self, sql) 596 print >>hyperdb.DEBUG, 'clear', (self, sql)
581 self.cursor.execute(sql) 597 self.cursor.execute(sql)
582 598
583 # 599 #
584 # Node IDs
585 #
586 def newid(self, classname):
587 ''' Generate a new id for the given class
588 '''
589 # get the next ID
590 sql = 'select num from ids where name=%s'%self.arg
591 if __debug__:
592 print >>hyperdb.DEBUG, 'newid', (self, sql, classname)
593 self.cursor.execute(sql, (classname, ))
594 newid = int(self.cursor.fetchone()[0])
595
596 # update the counter
597 sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg)
598 vals = (int(newid)+1, classname)
599 if __debug__:
600 print >>hyperdb.DEBUG, 'newid', (self, sql, vals)
601 self.cursor.execute(sql, vals)
602
603 # return as string
604 return str(newid)
605
606 def setid(self, classname, setid):
607 ''' Set the id counter: used during import of database
608 '''
609 sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg)
610 vals = (setid, classname)
611 if __debug__:
612 print >>hyperdb.DEBUG, 'setid', (self, sql, vals)
613 self.cursor.execute(sql, vals)
614
615 #
616 # Nodes 600 # Nodes
617 # 601 #
602
603 hyperdb_to_sql_value = {
604 hyperdb.String : str,
605 hyperdb.Date : lambda x: x.formal(sep=' ', sec='%f'),
606 hyperdb.Link : int,
607 hyperdb.Interval : lambda x: x.serialise(),
608 hyperdb.Password : str,
609 hyperdb.Boolean : int,
610 hyperdb.Number : lambda x: x,
611 }
618 def addnode(self, classname, nodeid, node): 612 def addnode(self, classname, nodeid, node):
619 ''' Add the specified node to its class's db. 613 ''' Add the specified node to its class's db.
620 ''' 614 '''
621 if __debug__: 615 if __debug__:
622 print >>hyperdb.DEBUG, 'addnode', (self, classname, nodeid, node) 616 print >>hyperdb.DEBUG, 'addnode', (self, classname, nodeid, node)
624 # determine the column definitions and multilink tables 618 # determine the column definitions and multilink tables
625 cl = self.classes[classname] 619 cl = self.classes[classname]
626 cols, mls = self.determine_columns(cl.properties.items()) 620 cols, mls = self.determine_columns(cl.properties.items())
627 621
628 # we'll be supplied these props if we're doing an import 622 # we'll be supplied these props if we're doing an import
629 if not node.has_key('creator'): 623 values = node.copy()
624 if not values.has_key('creator'):
630 # add in the "calculated" properties (dupe so we don't affect 625 # add in the "calculated" properties (dupe so we don't affect
631 # calling code's node assumptions) 626 # calling code's node assumptions)
632 node = node.copy() 627 values['creation'] = values['activity'] = date.Date()
633 node['creation'] = node['activity'] = date.Date() 628 values['actor'] = values['creator'] = self.getuid()
634 node['actor'] = node['creator'] = self.getuid() 629
630 cl = self.classes[classname]
631 props = cl.getprops(protected=1)
632 del props['id']
635 633
636 # default the non-multilink columns 634 # default the non-multilink columns
637 for col, prop in cl.properties.items(): 635 for col, prop in props.items():
638 if not node.has_key(col): 636 if not values.has_key(col):
639 if isinstance(prop, Multilink): 637 if isinstance(prop, Multilink):
640 node[col] = [] 638 values[col] = []
641 else: 639 else:
642 node[col] = None 640 values[col] = None
643 641
644 # clear this node out of the cache if it's in there 642 # clear this node out of the cache if it's in there
645 key = (classname, nodeid) 643 key = (classname, nodeid)
646 if self.cache.has_key(key): 644 if self.cache.has_key(key):
647 del self.cache[key] 645 del self.cache[key]
648 self.cache_lru.remove(key) 646 self.cache_lru.remove(key)
649 647
650 # make the node data safe for the DB 648 # figure the values to insert
651 node = self.serialise(classname, node) 649 vals = []
650 for col,dt in cols:
651 prop = props[col[1:]]
652 value = values[col[1:]]
653 if value:
654 value = self.hyperdb_to_sql_value[prop.__class__](value)
655 vals.append(value)
656 vals.append(nodeid)
657 vals = tuple(vals)
652 658
653 # make sure the ordering is correct for column name -> column value 659 # make sure the ordering is correct for column name -> column value
654 vals = tuple([node[col[1:]] for col in cols]) + (nodeid, 0) 660 s = ','.join([self.arg for x in cols]) + ',%s'%self.arg
655 s = ','.join([self.arg for x in cols]) + ',%s,%s'%(self.arg, self.arg) 661 cols = ','.join([col for col,dt in cols]) + ',id'
656 cols = ','.join(cols) + ',id,__retired__'
657 662
658 # perform the inserts 663 # perform the inserts
659 sql = 'insert into _%s (%s) values (%s)'%(classname, cols, s) 664 sql = 'insert into _%s (%s) values (%s)'%(classname, cols, s)
660 if __debug__: 665 if __debug__:
661 print >>hyperdb.DEBUG, 'addnode', (self, sql, vals) 666 print >>hyperdb.DEBUG, 'addnode', (self, sql, vals)
687 # add the special props 692 # add the special props
688 values = values.copy() 693 values = values.copy()
689 values['activity'] = date.Date() 694 values['activity'] = date.Date()
690 values['actor'] = self.getuid() 695 values['actor'] = self.getuid()
691 696
692 # make db-friendly
693 values = self.serialise(classname, values)
694
695 cl = self.classes[classname] 697 cl = self.classes[classname]
698 props = cl.getprops()
699
696 cols = [] 700 cols = []
697 mls = [] 701 mls = []
698 # add the multilinks separately 702 # add the multilinks separately
699 props = cl.getprops()
700 for col in values.keys(): 703 for col in values.keys():
701 prop = props[col] 704 prop = props[col]
702 if isinstance(prop, Multilink): 705 if isinstance(prop, Multilink):
703 mls.append(col) 706 mls.append(col)
704 else: 707 else:
705 cols.append('_'+col) 708 cols.append(col)
706 cols.sort() 709 cols.sort()
710
711 # figure the values to insert
712 vals = []
713 for col in cols:
714 prop = props[col]
715 value = values[col]
716 if value is not None:
717 value = self.hyperdb_to_sql_value[prop.__class__](value)
718 vals.append(value)
719 vals.append(int(nodeid))
720 vals = tuple(vals)
707 721
708 # if there's any updates to regular columns, do them 722 # if there's any updates to regular columns, do them
709 if cols: 723 if cols:
710 # make sure the ordering is correct for column name -> column value 724 # make sure the ordering is correct for column name -> column value
711 sqlvals = tuple([values[col[1:]] for col in cols]) + (nodeid,) 725 s = ','.join(['_%s=%s'%(x, self.arg) for x in cols])
712 s = ','.join(['%s=%s'%(x, self.arg) for x in cols])
713 cols = ','.join(cols) 726 cols = ','.join(cols)
714 727
715 # perform the update 728 # perform the update
716 sql = 'update _%s set %s where id=%s'%(classname, s, self.arg) 729 sql = 'update _%s set %s where id=%s'%(classname, s, self.arg)
717 if __debug__: 730 if __debug__:
718 print >>hyperdb.DEBUG, 'setnode', (self, sql, sqlvals) 731 print >>hyperdb.DEBUG, 'setnode', (self, sql, vals)
719 self.cursor.execute(sql, sqlvals) 732 self.cursor.execute(sql, vals)
720 733
721 # now the fun bit, updating the multilinks ;) 734 # now the fun bit, updating the multilinks ;)
722 for col, (add, remove) in multilink_changes.items(): 735 for col, (add, remove) in multilink_changes.items():
723 tn = '%s_%s'%(classname, col) 736 tn = '%s_%s'%(classname, col)
724 if add: 737 if add:
725 sql = 'insert into %s (nodeid, linkid) values (%s,%s)'%(tn, 738 sql = 'insert into %s (nodeid, linkid) values (%s,%s)'%(tn,
726 self.arg, self.arg) 739 self.arg, self.arg)
727 for addid in add: 740 for addid in add:
728 self.sql(sql, (nodeid, addid)) 741 # XXX numeric ids
742 self.sql(sql, (int(nodeid), int(addid)))
729 if remove: 743 if remove:
730 sql = 'delete from %s where nodeid=%s and linkid=%s'%(tn, 744 sql = 'delete from %s where nodeid=%s and linkid=%s'%(tn,
731 self.arg, self.arg) 745 self.arg, self.arg)
732 for removeid in remove: 746 for removeid in remove:
733 self.sql(sql, (nodeid, removeid)) 747 # XXX numeric ids
748 self.sql(sql, (int(nodeid), int(removeid)))
734 749
735 # make sure we do the commit-time extra stuff for this node 750 # make sure we do the commit-time extra stuff for this node
736 self.transactions.append((self.doSaveNode, (classname, nodeid, values))) 751 self.transactions.append((self.doSaveNode, (classname, nodeid, values)))
737 752
753 sql_to_hyperdb_value = {
754 hyperdb.String : str,
755 hyperdb.Date : date.Date,
756 # hyperdb.Link : int, # XXX numeric ids
757 hyperdb.Link : str,
758 hyperdb.Interval : date.Interval,
759 hyperdb.Password : lambda x: password.Password(encrypted=x),
760 hyperdb.Boolean : int,
761 hyperdb.Number : _num_cvt,
762 }
738 def getnode(self, classname, nodeid): 763 def getnode(self, classname, nodeid):
739 ''' Get a node from the database. 764 ''' Get a node from the database.
740 ''' 765 '''
741 if __debug__: 766 if __debug__:
742 print >>hyperdb.DEBUG, 'getnode', (self, classname, nodeid) 767 print >>hyperdb.DEBUG, 'getnode', (self, classname, nodeid)
751 return self.cache[key] 776 return self.cache[key]
752 777
753 # figure the columns we're fetching 778 # figure the columns we're fetching
754 cl = self.classes[classname] 779 cl = self.classes[classname]
755 cols, mls = self.determine_columns(cl.properties.items()) 780 cols, mls = self.determine_columns(cl.properties.items())
756 scols = ','.join(cols) 781 scols = ','.join([col for col,dt in cols])
757 782
758 # perform the basic property fetch 783 # perform the basic property fetch
759 sql = 'select %s from _%s where id=%s'%(scols, classname, self.arg) 784 sql = 'select %s from _%s where id=%s'%(scols, classname, self.arg)
760 self.sql(sql, (nodeid,)) 785 self.sql(sql, (nodeid,))
761 786
763 if values is None: 788 if values is None:
764 raise IndexError, 'no such %s node %s'%(classname, nodeid) 789 raise IndexError, 'no such %s node %s'%(classname, nodeid)
765 790
766 # make up the node 791 # make up the node
767 node = {} 792 node = {}
793 props = cl.getprops(protected=1)
768 for col in range(len(cols)): 794 for col in range(len(cols)):
769 node[cols[col][1:]] = values[col] 795 name = cols[col][0][1:]
796 value = values[col]
797 if value is not None:
798 value = self.sql_to_hyperdb_value[props[name].__class__](value)
799 node[name] = value
800
770 801
771 # now the multilinks 802 # now the multilinks
772 for col in mls: 803 for col in mls:
773 # get the link ids 804 # get the link ids
774 sql = 'select linkid from %s_%s where nodeid=%s'%(classname, col, 805 sql = 'select linkid from %s_%s where nodeid=%s'%(classname, col,
775 self.arg) 806 self.arg)
776 self.cursor.execute(sql, (nodeid,)) 807 self.cursor.execute(sql, (nodeid,))
777 # extract the first column from the result 808 # extract the first column from the result
778 node[col] = [x[0] for x in self.cursor.fetchall()] 809 # XXX numeric ids
779 810 node[col] = [str(x[0]) for x in self.cursor.fetchall()]
780 # un-dbificate the node data
781 node = self.unserialise(classname, node)
782 811
783 # save off in the cache 812 # save off in the cache
784 key = (classname, nodeid) 813 key = (classname, nodeid)
785 self.cache[key] = node 814 self.cache[key] = node
786 # update the LRU 815 # update the LRU
824 853
825 # remove journal entries 854 # remove journal entries
826 sql = 'delete from %s__journal where nodeid=%s'%(classname, self.arg) 855 sql = 'delete from %s__journal where nodeid=%s'%(classname, self.arg)
827 self.sql(sql, (nodeid,)) 856 self.sql(sql, (nodeid,))
828 857
829 def serialise(self, classname, node):
830 '''Copy the node contents, converting non-marshallable data into
831 marshallable data.
832 '''
833 if __debug__:
834 print >>hyperdb.DEBUG, 'serialise', classname, node
835 properties = self.getclass(classname).getprops()
836 d = {}
837 for k, v in node.items():
838 # if the property doesn't exist, or is the "retired" flag then
839 # it won't be in the properties dict
840 if not properties.has_key(k):
841 d[k] = v
842 continue
843
844 # get the property spec
845 prop = properties[k]
846
847 if isinstance(prop, Password) and v is not None:
848 d[k] = str(v)
849 elif isinstance(prop, Date) and v is not None:
850 d[k] = v.serialise()
851 elif isinstance(prop, Interval) and v is not None:
852 d[k] = v.serialise()
853 else:
854 d[k] = v
855 return d
856
857 def unserialise(self, classname, node):
858 '''Decode the marshalled node data
859 '''
860 if __debug__:
861 print >>hyperdb.DEBUG, 'unserialise', classname, node
862 properties = self.getclass(classname).getprops()
863 d = {}
864 for k, v in node.items():
865 # if the property doesn't exist, or is the "retired" flag then
866 # it won't be in the properties dict
867 if not properties.has_key(k):
868 d[k] = v
869 continue
870
871 # get the property spec
872 prop = properties[k]
873
874 if isinstance(prop, Date) and v is not None:
875 d[k] = date.Date(v)
876 elif isinstance(prop, Interval) and v is not None:
877 d[k] = date.Interval(v)
878 elif isinstance(prop, Password) and v is not None:
879 p = password.Password()
880 p.unpack(v)
881 d[k] = p
882 elif isinstance(prop, Boolean) and v is not None:
883 d[k] = int(v)
884 elif isinstance(prop, Number) and v is not None:
885 # try int first, then assume it's a float
886 try:
887 d[k] = int(v)
888 except ValueError:
889 d[k] = float(v)
890 else:
891 d[k] = v
892 return d
893
894 def hasnode(self, classname, nodeid): 858 def hasnode(self, classname, nodeid):
895 ''' Determine if the database has a given node. 859 ''' Determine if the database has a given node.
896 ''' 860 '''
897 sql = 'select count(*) from _%s where id=%s'%(classname, self.arg) 861 sql = 'select count(*) from _%s where id=%s'%(classname, self.arg)
898 if __debug__: 862 if __debug__:
928 if creator: 892 if creator:
929 journaltag = creator 893 journaltag = creator
930 else: 894 else:
931 journaltag = self.getuid() 895 journaltag = self.getuid()
932 if creation: 896 if creation:
933 journaldate = creation.serialise() 897 journaldate = creation
934 else: 898 else:
935 journaldate = date.Date().serialise() 899 journaldate = date.Date()
936 900
937 # create the journal entry 901 # create the journal entry
938 cols = ','.join('nodeid date tag action params'.split()) 902 cols = ','.join('nodeid date tag action params'.split())
939 903
940 if __debug__: 904 if __debug__:
958 journaltag, action, params): 922 journaltag, action, params):
959 ''' Save the journal entry to the database 923 ''' Save the journal entry to the database
960 ''' 924 '''
961 # make the params db-friendly 925 # make the params db-friendly
962 params = repr(params) 926 params = repr(params)
963 entry = (nodeid, journaldate, journaltag, action, params) 927 dc = self.hyperdb_to_sql_value[hyperdb.Date]
928 entry = (nodeid, dc(journaldate), journaltag, action, params)
964 929
965 # do the insert 930 # do the insert
966 a = self.arg 931 a = self.arg
967 sql = 'insert into %s__journal (%s) values (%s,%s,%s,%s,%s)'%(classname, 932 sql = 'insert into %s__journal (%s) values (%s,%s,%s,%s,%s)'%(
968 cols, a, a, a, a, a) 933 classname, cols, a, a, a, a, a)
969 if __debug__: 934 if __debug__:
970 print >>hyperdb.DEBUG, 'addjournal', (self, sql, entry) 935 print >>hyperdb.DEBUG, 'addjournal', (self, sql, entry)
971 self.cursor.execute(sql, entry) 936 self.cursor.execute(sql, entry)
972 937
973 def load_journal(self, classname, cols, nodeid): 938 def load_journal(self, classname, cols, nodeid):
978 cols, classname, self.arg) 943 cols, classname, self.arg)
979 if __debug__: 944 if __debug__:
980 print >>hyperdb.DEBUG, 'load_journal', (self, sql, nodeid) 945 print >>hyperdb.DEBUG, 'load_journal', (self, sql, nodeid)
981 self.cursor.execute(sql, (nodeid,)) 946 self.cursor.execute(sql, (nodeid,))
982 res = [] 947 res = []
948 dc = self.sql_to_hyperdb_value[hyperdb.Date]
983 for nodeid, date_stamp, user, action, params in self.cursor.fetchall(): 949 for nodeid, date_stamp, user, action, params in self.cursor.fetchall():
984 params = eval(params) 950 params = eval(params)
985 res.append((nodeid, date.Date(date_stamp), user, action, params)) 951 # XXX numeric ids
952 res.append((str(nodeid), dc(date_stamp), user, action, params))
986 return res 953 return res
987 954
988 def pack(self, pack_before): 955 def pack(self, pack_before):
989 ''' Delete all journal entries except "create" before 'pack_before'. 956 ''' Delete all journal entries except "create" before 'pack_before'.
990 ''' 957 '''
1276 # done 1243 # done
1277 self.db.addnode(self.classname, newid, propvalues) 1244 self.db.addnode(self.classname, newid, propvalues)
1278 if self.do_journal: 1245 if self.do_journal:
1279 self.db.addjournal(self.classname, newid, 'create', {}) 1246 self.db.addjournal(self.classname, newid, 'create', {})
1280 1247
1281 return newid 1248 # XXX numeric ids
1249 return str(newid)
1282 1250
1283 def export_list(self, propnames, nodeid): 1251 def export_list(self, propnames, nodeid):
1284 ''' Export a node - generate a list of CSV-able data in the order 1252 ''' Export a node - generate a list of CSV-able data in the order
1285 specified by propnames for the given node. 1253 specified by propnames for the given node.
1286 ''' 1254 '''
1842 if not row: 1810 if not row:
1843 raise KeyError, 'No key (%s) value "%s" for "%s"'%(self.key, 1811 raise KeyError, 'No key (%s) value "%s" for "%s"'%(self.key,
1844 keyvalue, self.classname) 1812 keyvalue, self.classname)
1845 1813
1846 # return the id 1814 # return the id
1847 return row[0] 1815 # XXX numeric ids
1816 return str(row[0])
1848 1817
1849 def find(self, **propspec): 1818 def find(self, **propspec):
1850 '''Get the ids of nodes in this class which link to the given nodes. 1819 '''Get the ids of nodes in this class which link to the given nodes.
1851 1820
1852 'propspec' consists of keyword args propname=nodeid or 1821 'propspec' consists of keyword args propname=nodeid or
1921 elif len(o) > 1: 1890 elif len(o) > 1:
1922 o = '(' + ' or '.join(['(%s)'%i for i in o]) + ')' 1891 o = '(' + ' or '.join(['(%s)'%i for i in o]) + ')'
1923 else: 1892 else:
1924 o = o[0] 1893 o = o[0]
1925 t = ', '.join(tables) 1894 t = ', '.join(tables)
1926 sql = 'select distinct(id) from %s where __retired__ <> %s and %s'%(t, a, o) 1895 sql = 'select distinct(id) from %s where __retired__ <> %s and %s'%(
1896 t, a, o)
1927 self.db.sql(sql, allvalues) 1897 self.db.sql(sql, allvalues)
1928 l = [x[0] for x in self.db.sql_fetchall()] 1898 # XXX numeric ids
1899 l = [str(x[0]) for x in self.db.sql_fetchall()]
1929 if __debug__: 1900 if __debug__:
1930 print >>hyperdb.DEBUG, 'find ... ', l 1901 print >>hyperdb.DEBUG, 'find ... ', l
1931 return l 1902 return l
1932 1903
1933 def stringFind(self, **requirements): 1904 def stringFind(self, **requirements):
1951 s = ' and '.join(['lower(_%s)=%s'%(col, self.db.arg) for col in where]) 1922 s = ' and '.join(['lower(_%s)=%s'%(col, self.db.arg) for col in where])
1952 sql = 'select id from _%s where %s and __retired__=%s'%(self.classname, 1923 sql = 'select id from _%s where %s and __retired__=%s'%(self.classname,
1953 s, self.db.arg) 1924 s, self.db.arg)
1954 args.append(0) 1925 args.append(0)
1955 self.db.sql(sql, tuple(args)) 1926 self.db.sql(sql, tuple(args))
1956 l = [x[0] for x in self.db.sql_fetchall()] 1927 # XXX numeric ids
1928 l = [str(x[0]) for x in self.db.sql_fetchall()]
1957 if __debug__: 1929 if __debug__:
1958 print >>hyperdb.DEBUG, 'find ... ', l 1930 print >>hyperdb.DEBUG, 'find ... ', l
1959 return l 1931 return l
1960 1932
1961 def list(self): 1933 def list(self):
1981 args = () 1953 args = ()
1982 sql = 'select id from _%s'%self.classname 1954 sql = 'select id from _%s'%self.classname
1983 if __debug__: 1955 if __debug__:
1984 print >>hyperdb.DEBUG, 'getnodeids', (self, sql, retired) 1956 print >>hyperdb.DEBUG, 'getnodeids', (self, sql, retired)
1985 self.db.cursor.execute(sql, args) 1957 self.db.cursor.execute(sql, args)
1986 ids = [x[0] for x in self.db.cursor.fetchall()] 1958 # XXX numeric ids
1959 ids = [str(x[0]) for x in self.db.cursor.fetchall()]
1987 return ids 1960 return ids
1988 1961
1989 def filter(self, search_matches, filterspec, sort=(None,None), 1962 def filter(self, search_matches, filterspec, sort=(None,None),
1990 group=(None,None)): 1963 group=(None,None)):
1991 '''Return a list of the ids of the active nodes in this class that 1964 '''Return a list of the ids of the active nodes in this class that
2075 where.append('_%s is NULL'%k) 2048 where.append('_%s is NULL'%k)
2076 else: 2049 else:
2077 where.append('_%s=%s'%(k, a)) 2050 where.append('_%s=%s'%(k, a))
2078 args.append(v) 2051 args.append(v)
2079 elif isinstance(propclass, Date): 2052 elif isinstance(propclass, Date):
2053 dc = self.db.hyperdb_to_sql_value[hyperdb.Date]
2080 if isinstance(v, type([])): 2054 if isinstance(v, type([])):
2081 s = ','.join([a for x in v]) 2055 s = ','.join([a for x in v])
2082 where.append('_%s in (%s)'%(k, s)) 2056 where.append('_%s in (%s)'%(k, s))
2083 args = args + [date.Date(x).serialise() for x in v] 2057 args = args + [dc(date.Date(v)) for x in v]
2084 else: 2058 else:
2085 try: 2059 try:
2086 # Try to filter on range of dates 2060 # Try to filter on range of dates
2087 date_rng = Range(v, date.Date, offset=timezone) 2061 date_rng = Range(v, date.Date, offset=timezone)
2088 if (date_rng.from_value): 2062 if date_rng.from_value:
2089 where.append('_%s >= %s'%(k, a)) 2063 where.append('_%s >= %s'%(k, a))
2090 args.append(date_rng.from_value.serialise()) 2064 args.append(dc(date_rng.from_value))
2091 if (date_rng.to_value): 2065 if date_rng.to_value:
2092 where.append('_%s <= %s'%(k, a)) 2066 where.append('_%s <= %s'%(k, a))
2093 args.append(date_rng.to_value.serialise()) 2067 args.append(dc(date_rng.to_value))
2094 except ValueError: 2068 except ValueError:
2095 # If range creation fails - ignore that search parameter 2069 # If range creation fails - ignore that search parameter
2096 pass 2070 pass
2097 elif isinstance(propclass, Interval): 2071 elif isinstance(propclass, Interval):
2098 if isinstance(v, type([])): 2072 if isinstance(v, type([])):
2101 args = args + [date.Interval(x).serialise() for x in v] 2075 args = args + [date.Interval(x).serialise() for x in v]
2102 else: 2076 else:
2103 try: 2077 try:
2104 # Try to filter on range of intervals 2078 # Try to filter on range of intervals
2105 date_rng = Range(v, date.Interval) 2079 date_rng = Range(v, date.Interval)
2106 if (date_rng.from_value): 2080 if date_rng.from_value:
2107 where.append('_%s >= %s'%(k, a)) 2081 where.append('_%s >= %s'%(k, a))
2108 args.append(date_rng.from_value.serialise()) 2082 args.append(date_rng.from_value.serialise())
2109 if (date_rng.to_value): 2083 if date_rng.to_value:
2110 where.append('_%s <= %s'%(k, a)) 2084 where.append('_%s <= %s'%(k, a))
2111 args.append(date_rng.to_value.serialise()) 2085 args.append(date_rng.to_value.serialise())
2112 except ValueError: 2086 except ValueError:
2113 # If range creation fails - ignore that search parameter 2087 # If range creation fails - ignore that search parameter
2114 pass 2088 pass
2186 # psycopg doesn't like empty args 2160 # psycopg doesn't like empty args
2187 self.db.cursor.execute(sql) 2161 self.db.cursor.execute(sql)
2188 l = self.db.sql_fetchall() 2162 l = self.db.sql_fetchall()
2189 2163
2190 # return the IDs (the first column) 2164 # return the IDs (the first column)
2191 return [row[0] for row in l] 2165 # XXX numeric ids
2166 return [str(row[0]) for row in l]
2192 2167
2193 def count(self): 2168 def count(self):
2194 '''Get the number of nodes in this class. 2169 '''Get the number of nodes in this class.
2195 2170
2196 If the returned integer is 'numnodes', the ids of all the nodes 2171 If the returned integer is 'numnodes', the ids of all the nodes

Roundup Issue Tracker: http://roundup-tracker.org/