Mercurial > p > roundup > code
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 |
