Mercurial > p > roundup > code
comparison roundup/backends/rdbms_common.py @ 1183:08a13a84ed43
Some speedups - both of the SQL backends can handle using only one cursor.
Better date unserialisation too.
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Mon, 23 Sep 2002 06:48:35 +0000 |
| parents | 49aebf5a8691 |
| children | 3b0735ef8207 |
comparison
equal
deleted
inserted
replaced
| 1182:cb35cf3db0e0 | 1183:08a13a84ed43 |
|---|---|
| 1 # $Id: rdbms_common.py,v 1.10 2002-09-23 00:50:32 richard Exp $ | 1 # $Id: rdbms_common.py,v 1.11 2002-09-23 06:48:35 richard Exp $ |
| 2 | 2 |
| 3 # standard python modules | 3 # standard python modules |
| 4 import sys, os, time, re, errno, weakref, copy | 4 import sys, os, time, re, errno, weakref, copy |
| 5 | 5 |
| 6 # roundup modules | 6 # roundup modules |
| 51 def open_connection(self): | 51 def open_connection(self): |
| 52 ''' Open a connection to the database, creating it if necessary | 52 ''' Open a connection to the database, creating it if necessary |
| 53 ''' | 53 ''' |
| 54 raise NotImplemented | 54 raise NotImplemented |
| 55 | 55 |
| 56 def sql(self, cursor, sql, args=None): | 56 def sql(self, sql, args=None): |
| 57 ''' Execute the sql with the optional args. | 57 ''' Execute the sql with the optional args. |
| 58 ''' | 58 ''' |
| 59 if __debug__: | 59 if __debug__: |
| 60 print >>hyperdb.DEBUG, (self, sql, args) | 60 print >>hyperdb.DEBUG, (self, sql, args) |
| 61 if args: | 61 if args: |
| 62 cursor.execute(sql, args) | 62 self.cursor.execute(sql, args) |
| 63 else: | 63 else: |
| 64 cursor.execute(sql) | 64 self.cursor.execute(sql) |
| 65 | 65 |
| 66 def sql_fetchone(self, cursor): | 66 def sql_fetchone(self): |
| 67 ''' Fetch a single row. If there's nothing to fetch, return None. | 67 ''' Fetch a single row. If there's nothing to fetch, return None. |
| 68 ''' | 68 ''' |
| 69 raise NotImplemented | 69 raise NotImplemented |
| 70 | 70 |
| 71 def sql_stringquote(self, value): | 71 def sql_stringquote(self, value): |
| 72 ''' Quote the string so it's safe to put in the 'sql quotes' | 72 ''' Quote the string so it's safe to put in the 'sql quotes' |
| 73 ''' | 73 ''' |
| 74 return re.sub("'", "''", str(value)) | 74 return re.sub("'", "''", str(value)) |
| 75 | 75 |
| 76 def save_dbschema(self, cursor, schema): | 76 def save_dbschema(self, schema): |
| 77 ''' Save the schema definition that the database currently implements | 77 ''' Save the schema definition that the database currently implements |
| 78 ''' | 78 ''' |
| 79 raise NotImplemented | 79 raise NotImplemented |
| 80 | 80 |
| 81 def load_dbschema(self, cursor): | 81 def load_dbschema(self): |
| 82 ''' Load the schema definition that the database currently implements | 82 ''' Load the schema definition that the database currently implements |
| 83 ''' | 83 ''' |
| 84 raise NotImplemented | 84 raise NotImplemented |
| 85 | 85 |
| 86 def post_init(self): | 86 def post_init(self): |
| 106 if not self.classes.has_key(classname): | 106 if not self.classes.has_key(classname): |
| 107 self.drop_class(classname) | 107 self.drop_class(classname) |
| 108 | 108 |
| 109 # update the database version of the schema | 109 # update the database version of the schema |
| 110 if save: | 110 if save: |
| 111 cursor = self.conn.cursor() | 111 self.sql('delete from schema') |
| 112 self.sql(cursor, 'delete from schema') | 112 self.save_dbschema(self.database_schema) |
| 113 self.save_dbschema(cursor, self.database_schema) | |
| 114 | 113 |
| 115 # reindex the db if necessary | 114 # reindex the db if necessary |
| 116 if self.indexer.should_reindex(): | 115 if self.indexer.should_reindex(): |
| 117 self.reindex() | 116 self.reindex() |
| 118 | 117 |
| 178 dbspec_propnames,dbspec_props = [],{} | 177 dbspec_propnames,dbspec_props = [],{} |
| 179 for propname,prop in dbspec[1]: | 178 for propname,prop in dbspec[1]: |
| 180 dbspec_propnames.append(propname) | 179 dbspec_propnames.append(propname) |
| 181 dbspec_props[propname] = prop | 180 dbspec_props[propname] = prop |
| 182 | 181 |
| 183 # we're going to need one of these | |
| 184 cursor = self.conn.cursor() | |
| 185 | |
| 186 # now compare | 182 # now compare |
| 187 for propname in spec_propnames: | 183 for propname in spec_propnames: |
| 188 prop = spec_props[propname] | 184 prop = spec_props[propname] |
| 189 if dbspec_props.has_key(propname) and prop==dbspec_props[propname]: | 185 if dbspec_props.has_key(propname) and prop==dbspec_props[propname]: |
| 190 continue | 186 continue |
| 193 | 189 |
| 194 if not dbspec_props.has_key(propname): | 190 if not dbspec_props.has_key(propname): |
| 195 # add the property | 191 # add the property |
| 196 if isinstance(prop, Multilink): | 192 if isinstance(prop, Multilink): |
| 197 # all we have to do here is create a new table, easy! | 193 # all we have to do here is create a new table, easy! |
| 198 self.create_multilink_table(cursor, spec, propname) | 194 self.create_multilink_table(spec, propname) |
| 199 continue | 195 continue |
| 200 | 196 |
| 201 # no ALTER TABLE, so we: | 197 # no ALTER TABLE, so we: |
| 202 # 1. pull out the data, including an extra None column | 198 # 1. pull out the data, including an extra None column |
| 203 oldcols, x = self.determine_columns(dbspec[1]) | 199 oldcols, x = self.determine_columns(dbspec[1]) |
| 205 oldcols.append('__retired__') | 201 oldcols.append('__retired__') |
| 206 cn = spec.classname | 202 cn = spec.classname |
| 207 sql = 'select %s,%s from _%s'%(','.join(oldcols), self.arg, cn) | 203 sql = 'select %s,%s from _%s'%(','.join(oldcols), self.arg, cn) |
| 208 if __debug__: | 204 if __debug__: |
| 209 print >>hyperdb.DEBUG, 'update_class', (self, sql, None) | 205 print >>hyperdb.DEBUG, 'update_class', (self, sql, None) |
| 210 cursor.execute(sql, (None,)) | 206 self.cursor.execute(sql, (None,)) |
| 211 olddata = cursor.fetchall() | 207 olddata = self.cursor.fetchall() |
| 212 | 208 |
| 213 # 2. drop the old table | 209 # 2. drop the old table |
| 214 cursor.execute('drop table _%s'%cn) | 210 self.cursor.execute('drop table _%s'%cn) |
| 215 | 211 |
| 216 # 3. create the new table | 212 # 3. create the new table |
| 217 cols, mls = self.create_class_table(cursor, spec) | 213 cols, mls = self.create_class_table(spec) |
| 218 # ensure the new column is last | 214 # ensure the new column is last |
| 219 cols.remove('_'+propname) | 215 cols.remove('_'+propname) |
| 220 assert oldcols == cols, "Column lists don't match!" | 216 assert oldcols == cols, "Column lists don't match!" |
| 221 cols.append('_'+propname) | 217 cols.append('_'+propname) |
| 222 | 218 |
| 230 # XXX this isn't necessary in sqlite :( | 226 # XXX this isn't necessary in sqlite :( |
| 231 self.conn.commit() | 227 self.conn.commit() |
| 232 | 228 |
| 233 # do the insert | 229 # do the insert |
| 234 for row in olddata: | 230 for row in olddata: |
| 235 self.sql(cursor, sql, tuple(row)) | 231 self.sql(sql, tuple(row)) |
| 236 | 232 |
| 237 else: | 233 else: |
| 238 # modify the property | 234 # modify the property |
| 239 if __debug__: | 235 if __debug__: |
| 240 print >>hyperdb.DEBUG, 'update_class NOOP' | 236 print >>hyperdb.DEBUG, 'update_class NOOP' |
| 251 # delete the property | 247 # delete the property |
| 252 if isinstance(prop, Multilink): | 248 if isinstance(prop, Multilink): |
| 253 sql = 'drop table %s_%s'%(spec.classname, prop) | 249 sql = 'drop table %s_%s'%(spec.classname, prop) |
| 254 if __debug__: | 250 if __debug__: |
| 255 print >>hyperdb.DEBUG, 'update_class', (self, sql) | 251 print >>hyperdb.DEBUG, 'update_class', (self, sql) |
| 256 cursor.execute(sql) | 252 self.cursor.execute(sql) |
| 257 else: | 253 else: |
| 258 # no ALTER TABLE, so we: | 254 # no ALTER TABLE, so we: |
| 259 # 1. pull out the data, excluding the removed column | 255 # 1. pull out the data, excluding the removed column |
| 260 oldcols, x = self.determine_columns(spec.properties.items()) | 256 oldcols, x = self.determine_columns(spec.properties.items()) |
| 261 oldcols.append('id') | 257 oldcols.append('id') |
| 262 oldcols.append('__retired__') | 258 oldcols.append('__retired__') |
| 263 # remove the missing column | 259 # remove the missing column |
| 264 oldcols.remove('_'+propname) | 260 oldcols.remove('_'+propname) |
| 265 cn = spec.classname | 261 cn = spec.classname |
| 266 sql = 'select %s from _%s'%(','.join(oldcols), cn) | 262 sql = 'select %s from _%s'%(','.join(oldcols), cn) |
| 267 cursor.execute(sql, (None,)) | 263 self.cursor.execute(sql, (None,)) |
| 268 olddata = sql.fetchall() | 264 olddata = sql.fetchall() |
| 269 | 265 |
| 270 # 2. drop the old table | 266 # 2. drop the old table |
| 271 cursor.execute('drop table _%s'%cn) | 267 self.cursor.execute('drop table _%s'%cn) |
| 272 | 268 |
| 273 # 3. create the new table | 269 # 3. create the new table |
| 274 cols, mls = self.create_class_table(self, cursor, spec) | 270 cols, mls = self.create_class_table(self, spec) |
| 275 assert oldcols != cols, "Column lists don't match!" | 271 assert oldcols != cols, "Column lists don't match!" |
| 276 | 272 |
| 277 # 4. populate with the data from step one | 273 # 4. populate with the data from step one |
| 278 qs = ','.join([self.arg for x in cols]) | 274 qs = ','.join([self.arg for x in cols]) |
| 279 sql = 'insert into _%s values (%s)'%(cn, s) | 275 sql = 'insert into _%s values (%s)'%(cn, s) |
| 280 cursor.execute(sql, olddata) | 276 self.cursor.execute(sql, olddata) |
| 281 return 1 | 277 return 1 |
| 282 | 278 |
| 283 def create_class_table(self, cursor, spec): | 279 def create_class_table(self, spec): |
| 284 ''' create the class table for the given spec | 280 ''' create the class table for the given spec |
| 285 ''' | 281 ''' |
| 286 cols, mls = self.determine_columns(spec.properties.items()) | 282 cols, mls = self.determine_columns(spec.properties.items()) |
| 287 | 283 |
| 288 # add on our special columns | 284 # add on our special columns |
| 292 # create the base table | 288 # create the base table |
| 293 scols = ','.join(['%s varchar'%x for x in cols]) | 289 scols = ','.join(['%s varchar'%x for x in cols]) |
| 294 sql = 'create table _%s (%s)'%(spec.classname, scols) | 290 sql = 'create table _%s (%s)'%(spec.classname, scols) |
| 295 if __debug__: | 291 if __debug__: |
| 296 print >>hyperdb.DEBUG, 'create_class', (self, sql) | 292 print >>hyperdb.DEBUG, 'create_class', (self, sql) |
| 297 cursor.execute(sql) | 293 self.cursor.execute(sql) |
| 298 | 294 |
| 299 return cols, mls | 295 return cols, mls |
| 300 | 296 |
| 301 def create_journal_table(self, cursor, spec): | 297 def create_journal_table(self, spec): |
| 302 ''' create the journal table for a class given the spec and | 298 ''' create the journal table for a class given the spec and |
| 303 already-determined cols | 299 already-determined cols |
| 304 ''' | 300 ''' |
| 305 # journal table | 301 # journal table |
| 306 cols = ','.join(['%s varchar'%x | 302 cols = ','.join(['%s varchar'%x |
| 307 for x in 'nodeid date tag action params'.split()]) | 303 for x in 'nodeid date tag action params'.split()]) |
| 308 sql = 'create table %s__journal (%s)'%(spec.classname, cols) | 304 sql = 'create table %s__journal (%s)'%(spec.classname, cols) |
| 309 if __debug__: | 305 if __debug__: |
| 310 print >>hyperdb.DEBUG, 'create_class', (self, sql) | 306 print >>hyperdb.DEBUG, 'create_class', (self, sql) |
| 311 cursor.execute(sql) | 307 self.cursor.execute(sql) |
| 312 | 308 |
| 313 def create_multilink_table(self, cursor, spec, ml): | 309 def create_multilink_table(self, spec, ml): |
| 314 ''' Create a multilink table for the "ml" property of the class | 310 ''' Create a multilink table for the "ml" property of the class |
| 315 given by the spec | 311 given by the spec |
| 316 ''' | 312 ''' |
| 317 sql = 'create table %s_%s (linkid varchar, nodeid varchar)'%( | 313 sql = 'create table %s_%s (linkid varchar, nodeid varchar)'%( |
| 318 spec.classname, ml) | 314 spec.classname, ml) |
| 319 if __debug__: | 315 if __debug__: |
| 320 print >>hyperdb.DEBUG, 'create_class', (self, sql) | 316 print >>hyperdb.DEBUG, 'create_class', (self, sql) |
| 321 cursor.execute(sql) | 317 self.cursor.execute(sql) |
| 322 | 318 |
| 323 def create_class(self, spec): | 319 def create_class(self, spec): |
| 324 ''' Create a database table according to the given spec. | 320 ''' Create a database table according to the given spec. |
| 325 ''' | 321 ''' |
| 326 cursor = self.conn.cursor() | 322 cols, mls = self.create_class_table(spec) |
| 327 cols, mls = self.create_class_table(cursor, spec) | 323 self.create_journal_table(spec) |
| 328 self.create_journal_table(cursor, spec) | |
| 329 | 324 |
| 330 # now create the multilink tables | 325 # now create the multilink tables |
| 331 for ml in mls: | 326 for ml in mls: |
| 332 self.create_multilink_table(cursor, spec, ml) | 327 self.create_multilink_table(spec, ml) |
| 333 | 328 |
| 334 # ID counter | 329 # ID counter |
| 335 sql = 'insert into ids (name, num) values (%s,%s)'%(self.arg, self.arg) | 330 sql = 'insert into ids (name, num) values (%s,%s)'%(self.arg, self.arg) |
| 336 vals = (spec.classname, 1) | 331 vals = (spec.classname, 1) |
| 337 if __debug__: | 332 if __debug__: |
| 338 print >>hyperdb.DEBUG, 'create_class', (self, sql, vals) | 333 print >>hyperdb.DEBUG, 'create_class', (self, sql, vals) |
| 339 cursor.execute(sql, vals) | 334 self.cursor.execute(sql, vals) |
| 340 | 335 |
| 341 def drop_class(self, spec): | 336 def drop_class(self, spec): |
| 342 ''' Drop the given table from the database. | 337 ''' Drop the given table from the database. |
| 343 | 338 |
| 344 Drop the journal and multilink tables too. | 339 Drop the journal and multilink tables too. |
| 346 # figure the multilinks | 341 # figure the multilinks |
| 347 mls = [] | 342 mls = [] |
| 348 for col, prop in spec.properties.items(): | 343 for col, prop in spec.properties.items(): |
| 349 if isinstance(prop, Multilink): | 344 if isinstance(prop, Multilink): |
| 350 mls.append(col) | 345 mls.append(col) |
| 351 cursor = self.conn.cursor() | |
| 352 | 346 |
| 353 sql = 'drop table _%s'%spec.classname | 347 sql = 'drop table _%s'%spec.classname |
| 354 if __debug__: | 348 if __debug__: |
| 355 print >>hyperdb.DEBUG, 'drop_class', (self, sql) | 349 print >>hyperdb.DEBUG, 'drop_class', (self, sql) |
| 356 cursor.execute(sql) | 350 self.cursor.execute(sql) |
| 357 | 351 |
| 358 sql = 'drop table %s__journal'%spec.classname | 352 sql = 'drop table %s__journal'%spec.classname |
| 359 if __debug__: | 353 if __debug__: |
| 360 print >>hyperdb.DEBUG, 'drop_class', (self, sql) | 354 print >>hyperdb.DEBUG, 'drop_class', (self, sql) |
| 361 cursor.execute(sql) | 355 self.cursor.execute(sql) |
| 362 | 356 |
| 363 for ml in mls: | 357 for ml in mls: |
| 364 sql = 'drop table %s_%s'%(spec.classname, ml) | 358 sql = 'drop table %s_%s'%(spec.classname, ml) |
| 365 if __debug__: | 359 if __debug__: |
| 366 print >>hyperdb.DEBUG, 'drop_class', (self, sql) | 360 print >>hyperdb.DEBUG, 'drop_class', (self, sql) |
| 367 cursor.execute(sql) | 361 self.cursor.execute(sql) |
| 368 | 362 |
| 369 # | 363 # |
| 370 # Classes | 364 # Classes |
| 371 # | 365 # |
| 372 def __getattr__(self, classname): | 366 def __getattr__(self, classname): |
| 415 Note: I don't commit here, which is different behaviour to the | 409 Note: I don't commit here, which is different behaviour to the |
| 416 "nuke from orbit" behaviour in the *dbms. | 410 "nuke from orbit" behaviour in the *dbms. |
| 417 ''' | 411 ''' |
| 418 if __debug__: | 412 if __debug__: |
| 419 print >>hyperdb.DEBUG, 'clear', (self,) | 413 print >>hyperdb.DEBUG, 'clear', (self,) |
| 420 cursor = self.conn.cursor() | |
| 421 for cn in self.classes.keys(): | 414 for cn in self.classes.keys(): |
| 422 sql = 'delete from _%s'%cn | 415 sql = 'delete from _%s'%cn |
| 423 if __debug__: | 416 if __debug__: |
| 424 print >>hyperdb.DEBUG, 'clear', (self, sql) | 417 print >>hyperdb.DEBUG, 'clear', (self, sql) |
| 425 cursor.execute(sql) | 418 self.cursor.execute(sql) |
| 426 | 419 |
| 427 # | 420 # |
| 428 # Node IDs | 421 # Node IDs |
| 429 # | 422 # |
| 430 def newid(self, classname): | 423 def newid(self, classname): |
| 431 ''' Generate a new id for the given class | 424 ''' Generate a new id for the given class |
| 432 ''' | 425 ''' |
| 433 # get the next ID | 426 # get the next ID |
| 434 cursor = self.conn.cursor() | |
| 435 sql = 'select num from ids where name=%s'%self.arg | 427 sql = 'select num from ids where name=%s'%self.arg |
| 436 if __debug__: | 428 if __debug__: |
| 437 print >>hyperdb.DEBUG, 'newid', (self, sql, classname) | 429 print >>hyperdb.DEBUG, 'newid', (self, sql, classname) |
| 438 cursor.execute(sql, (classname, )) | 430 self.cursor.execute(sql, (classname, )) |
| 439 newid = cursor.fetchone()[0] | 431 newid = self.cursor.fetchone()[0] |
| 440 | 432 |
| 441 # update the counter | 433 # update the counter |
| 442 sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg) | 434 sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg) |
| 443 vals = (int(newid)+1, classname) | 435 vals = (int(newid)+1, classname) |
| 444 if __debug__: | 436 if __debug__: |
| 445 print >>hyperdb.DEBUG, 'newid', (self, sql, vals) | 437 print >>hyperdb.DEBUG, 'newid', (self, sql, vals) |
| 446 cursor.execute(sql, vals) | 438 self.cursor.execute(sql, vals) |
| 447 | 439 |
| 448 # return as string | 440 # return as string |
| 449 return str(newid) | 441 return str(newid) |
| 450 | 442 |
| 451 def setid(self, classname, setid): | 443 def setid(self, classname, setid): |
| 452 ''' Set the id counter: used during import of database | 444 ''' Set the id counter: used during import of database |
| 453 ''' | 445 ''' |
| 454 cursor = self.conn.cursor() | |
| 455 sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg) | 446 sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg) |
| 456 vals = (setid, classname) | 447 vals = (setid, classname) |
| 457 if __debug__: | 448 if __debug__: |
| 458 print >>hyperdb.DEBUG, 'setid', (self, sql, vals) | 449 print >>hyperdb.DEBUG, 'setid', (self, sql, vals) |
| 459 cursor.execute(sql, vals) | 450 self.cursor.execute(sql, vals) |
| 460 | 451 |
| 461 # | 452 # |
| 462 # Nodes | 453 # Nodes |
| 463 # | 454 # |
| 464 | 455 |
| 495 vals = tuple([node[col[1:]] for col in cols]) + (nodeid, 0) | 486 vals = tuple([node[col[1:]] for col in cols]) + (nodeid, 0) |
| 496 s = ','.join([self.arg for x in cols]) + ',%s,%s'%(self.arg, self.arg) | 487 s = ','.join([self.arg for x in cols]) + ',%s,%s'%(self.arg, self.arg) |
| 497 cols = ','.join(cols) + ',id,__retired__' | 488 cols = ','.join(cols) + ',id,__retired__' |
| 498 | 489 |
| 499 # perform the inserts | 490 # perform the inserts |
| 500 cursor = self.conn.cursor() | |
| 501 sql = 'insert into _%s (%s) values (%s)'%(classname, cols, s) | 491 sql = 'insert into _%s (%s) values (%s)'%(classname, cols, s) |
| 502 if __debug__: | 492 if __debug__: |
| 503 print >>hyperdb.DEBUG, 'addnode', (self, sql, vals) | 493 print >>hyperdb.DEBUG, 'addnode', (self, sql, vals) |
| 504 cursor.execute(sql, vals) | 494 self.cursor.execute(sql, vals) |
| 505 | 495 |
| 506 # insert the multilink rows | 496 # insert the multilink rows |
| 507 for col in mls: | 497 for col in mls: |
| 508 t = '%s_%s'%(classname, col) | 498 t = '%s_%s'%(classname, col) |
| 509 for entry in node[col]: | 499 for entry in node[col]: |
| 510 sql = 'insert into %s (linkid, nodeid) values (%s,%s)'%(t, | 500 sql = 'insert into %s (linkid, nodeid) values (%s,%s)'%(t, |
| 511 self.arg, self.arg) | 501 self.arg, self.arg) |
| 512 self.sql(cursor, sql, (entry, nodeid)) | 502 self.sql(sql, (entry, nodeid)) |
| 513 | 503 |
| 514 # make sure we do the commit-time extra stuff for this node | 504 # make sure we do the commit-time extra stuff for this node |
| 515 self.transactions.append((self.doSaveNode, (classname, nodeid, node))) | 505 self.transactions.append((self.doSaveNode, (classname, nodeid, node))) |
| 516 | 506 |
| 517 def setnode(self, classname, nodeid, values, multilink_changes): | 507 def setnode(self, classname, nodeid, values, multilink_changes): |
| 544 mls.append(col) | 534 mls.append(col) |
| 545 else: | 535 else: |
| 546 cols.append('_'+col) | 536 cols.append('_'+col) |
| 547 cols.sort() | 537 cols.sort() |
| 548 | 538 |
| 549 cursor = self.conn.cursor() | |
| 550 | |
| 551 # if there's any updates to regular columns, do them | 539 # if there's any updates to regular columns, do them |
| 552 if cols: | 540 if cols: |
| 553 # make sure the ordering is correct for column name -> column value | 541 # make sure the ordering is correct for column name -> column value |
| 554 sqlvals = tuple([values[col[1:]] for col in cols]) + (nodeid,) | 542 sqlvals = tuple([values[col[1:]] for col in cols]) + (nodeid,) |
| 555 s = ','.join(['%s=%s'%(x, self.arg) for x in cols]) | 543 s = ','.join(['%s=%s'%(x, self.arg) for x in cols]) |
| 557 | 545 |
| 558 # perform the update | 546 # perform the update |
| 559 sql = 'update _%s set %s where id=%s'%(classname, s, self.arg) | 547 sql = 'update _%s set %s where id=%s'%(classname, s, self.arg) |
| 560 if __debug__: | 548 if __debug__: |
| 561 print >>hyperdb.DEBUG, 'setnode', (self, sql, sqlvals) | 549 print >>hyperdb.DEBUG, 'setnode', (self, sql, sqlvals) |
| 562 cursor.execute(sql, sqlvals) | 550 self.cursor.execute(sql, sqlvals) |
| 563 | 551 |
| 564 # now the fun bit, updating the multilinks ;) | 552 # now the fun bit, updating the multilinks ;) |
| 565 for col, (add, remove) in multilink_changes.items(): | 553 for col, (add, remove) in multilink_changes.items(): |
| 566 tn = '%s_%s'%(classname, col) | 554 tn = '%s_%s'%(classname, col) |
| 567 if add: | 555 if add: |
| 568 sql = 'insert into %s (nodeid, linkid) values (%s,%s)'%(tn, | 556 sql = 'insert into %s (nodeid, linkid) values (%s,%s)'%(tn, |
| 569 self.arg, self.arg) | 557 self.arg, self.arg) |
| 570 for addid in add: | 558 for addid in add: |
| 571 self.sql(cursor, sql, (nodeid, addid)) | 559 self.sql(sql, (nodeid, addid)) |
| 572 if remove: | 560 if remove: |
| 573 sql = 'delete from %s where nodeid=%s and linkid=%s'%(tn, | 561 sql = 'delete from %s where nodeid=%s and linkid=%s'%(tn, |
| 574 self.arg, self.arg) | 562 self.arg, self.arg) |
| 575 for removeid in remove: | 563 for removeid in remove: |
| 576 self.sql(cursor, sql, (nodeid, removeid)) | 564 self.sql(sql, (nodeid, removeid)) |
| 577 | 565 |
| 578 # make sure we do the commit-time extra stuff for this node | 566 # make sure we do the commit-time extra stuff for this node |
| 579 self.transactions.append((self.doSaveNode, (classname, nodeid, values))) | 567 self.transactions.append((self.doSaveNode, (classname, nodeid, values))) |
| 580 | 568 |
| 581 def getnode(self, classname, nodeid): | 569 def getnode(self, classname, nodeid): |
| 597 cl = self.classes[classname] | 585 cl = self.classes[classname] |
| 598 cols, mls = self.determine_columns(cl.properties.items()) | 586 cols, mls = self.determine_columns(cl.properties.items()) |
| 599 scols = ','.join(cols) | 587 scols = ','.join(cols) |
| 600 | 588 |
| 601 # perform the basic property fetch | 589 # perform the basic property fetch |
| 602 cursor = self.conn.cursor() | |
| 603 sql = 'select %s from _%s where id=%s'%(scols, classname, self.arg) | 590 sql = 'select %s from _%s where id=%s'%(scols, classname, self.arg) |
| 604 self.sql(cursor, sql, (nodeid,)) | 591 self.sql(sql, (nodeid,)) |
| 605 | 592 |
| 606 values = self.sql_fetchone(cursor) | 593 values = self.sql_fetchone() |
| 607 if values is None: | 594 if values is None: |
| 608 raise IndexError, 'no such %s node %s'%(classname, nodeid) | 595 raise IndexError, 'no such %s node %s'%(classname, nodeid) |
| 609 | 596 |
| 610 # make up the node | 597 # make up the node |
| 611 node = {} | 598 node = {} |
| 615 # now the multilinks | 602 # now the multilinks |
| 616 for col in mls: | 603 for col in mls: |
| 617 # get the link ids | 604 # get the link ids |
| 618 sql = 'select linkid from %s_%s where nodeid=%s'%(classname, col, | 605 sql = 'select linkid from %s_%s where nodeid=%s'%(classname, col, |
| 619 self.arg) | 606 self.arg) |
| 620 cursor.execute(sql, (nodeid,)) | 607 self.cursor.execute(sql, (nodeid,)) |
| 621 # extract the first column from the result | 608 # extract the first column from the result |
| 622 node[col] = [x[0] for x in cursor.fetchall()] | 609 node[col] = [x[0] for x in self.cursor.fetchall()] |
| 623 | 610 |
| 624 # un-dbificate the node data | 611 # un-dbificate the node data |
| 625 node = self.unserialise(classname, node) | 612 node = self.unserialise(classname, node) |
| 626 | 613 |
| 627 # save off in the cache | 614 # save off in the cache |
| 652 for entry in self.transactions[:]: | 639 for entry in self.transactions[:]: |
| 653 if entry[1][:2] == (classname, nodeid): | 640 if entry[1][:2] == (classname, nodeid): |
| 654 self.transactions.remove(entry) | 641 self.transactions.remove(entry) |
| 655 | 642 |
| 656 # now do the SQL | 643 # now do the SQL |
| 657 cursor = self.conn.cursor() | |
| 658 sql = 'delete from _%s where id=%s'%(classname, self.arg) | 644 sql = 'delete from _%s where id=%s'%(classname, self.arg) |
| 659 self.sql(cursor, sql, (nodeid,)) | 645 self.sql(sql, (nodeid,)) |
| 660 | 646 |
| 661 # remove from multilnks | 647 # remove from multilnks |
| 662 cl = self.getclass(classname) | 648 cl = self.getclass(classname) |
| 663 x, mls = self.determine_columns(cl.properties.items()) | 649 x, mls = self.determine_columns(cl.properties.items()) |
| 664 for col in mls: | 650 for col in mls: |
| 665 # get the link ids | 651 # get the link ids |
| 666 sql = 'delete from %s_%s where nodeid=%s'%(classname, col, self.arg) | 652 sql = 'delete from %s_%s where nodeid=%s'%(classname, col, self.arg) |
| 667 cursor.execute(sql, (nodeid,)) | 653 self.cursor.execute(sql, (nodeid,)) |
| 668 | 654 |
| 669 # remove journal entries | 655 # remove journal entries |
| 670 sql = 'delete from %s__journal where nodeid=%s'%(classname, self.arg) | 656 sql = 'delete from %s__journal where nodeid=%s'%(classname, self.arg) |
| 671 self.sql(cursor, sql, (nodeid,)) | 657 self.sql(sql, (nodeid,)) |
| 672 | 658 |
| 673 def serialise(self, classname, node): | 659 def serialise(self, classname, node): |
| 674 '''Copy the node contents, converting non-marshallable data into | 660 '''Copy the node contents, converting non-marshallable data into |
| 675 marshallable data. | 661 marshallable data. |
| 676 ''' | 662 ''' |
| 728 return d | 714 return d |
| 729 | 715 |
| 730 def hasnode(self, classname, nodeid): | 716 def hasnode(self, classname, nodeid): |
| 731 ''' Determine if the database has a given node. | 717 ''' Determine if the database has a given node. |
| 732 ''' | 718 ''' |
| 733 cursor = self.conn.cursor() | |
| 734 sql = 'select count(*) from _%s where id=%s'%(classname, self.arg) | 719 sql = 'select count(*) from _%s where id=%s'%(classname, self.arg) |
| 735 if __debug__: | 720 if __debug__: |
| 736 print >>hyperdb.DEBUG, 'hasnode', (self, sql, nodeid) | 721 print >>hyperdb.DEBUG, 'hasnode', (self, sql, nodeid) |
| 737 cursor.execute(sql, (nodeid,)) | 722 self.cursor.execute(sql, (nodeid,)) |
| 738 return int(cursor.fetchone()[0]) | 723 return int(self.cursor.fetchone()[0]) |
| 739 | 724 |
| 740 def countnodes(self, classname): | 725 def countnodes(self, classname): |
| 741 ''' Count the number of nodes that exist for a particular Class. | 726 ''' Count the number of nodes that exist for a particular Class. |
| 742 ''' | 727 ''' |
| 743 cursor = self.conn.cursor() | |
| 744 sql = 'select count(*) from _%s'%classname | 728 sql = 'select count(*) from _%s'%classname |
| 745 if __debug__: | 729 if __debug__: |
| 746 print >>hyperdb.DEBUG, 'countnodes', (self, sql) | 730 print >>hyperdb.DEBUG, 'countnodes', (self, sql) |
| 747 cursor.execute(sql) | 731 self.cursor.execute(sql) |
| 748 return cursor.fetchone()[0] | 732 return self.cursor.fetchone()[0] |
| 749 | 733 |
| 750 def getnodeids(self, classname, retired=0): | 734 def getnodeids(self, classname, retired=0): |
| 751 ''' Retrieve all the ids of the nodes for a particular Class. | 735 ''' Retrieve all the ids of the nodes for a particular Class. |
| 752 | 736 |
| 753 Set retired=None to get all nodes. Otherwise it'll get all the | 737 Set retired=None to get all nodes. Otherwise it'll get all the |
| 754 retired or non-retired nodes, depending on the flag. | 738 retired or non-retired nodes, depending on the flag. |
| 755 ''' | 739 ''' |
| 756 cursor = self.conn.cursor() | |
| 757 # flip the sense of the flag if we don't want all of them | 740 # flip the sense of the flag if we don't want all of them |
| 758 if retired is not None: | 741 if retired is not None: |
| 759 retired = not retired | 742 retired = not retired |
| 760 sql = 'select id from _%s where __retired__ <> %s'%(classname, self.arg) | 743 sql = 'select id from _%s where __retired__ <> %s'%(classname, self.arg) |
| 761 if __debug__: | 744 if __debug__: |
| 762 print >>hyperdb.DEBUG, 'getnodeids', (self, sql, retired) | 745 print >>hyperdb.DEBUG, 'getnodeids', (self, sql, retired) |
| 763 cursor.execute(sql, (retired,)) | 746 self.cursor.execute(sql, (retired,)) |
| 764 return [x[0] for x in cursor.fetchall()] | 747 return [x[0] for x in self.cursor.fetchall()] |
| 765 | 748 |
| 766 def addjournal(self, classname, nodeid, action, params, creator=None, | 749 def addjournal(self, classname, nodeid, action, params, creator=None, |
| 767 creation=None): | 750 creation=None): |
| 768 ''' Journal the Action | 751 ''' Journal the Action |
| 769 'action' may be: | 752 'action' may be: |
| 793 | 776 |
| 794 if __debug__: | 777 if __debug__: |
| 795 print >>hyperdb.DEBUG, 'addjournal', (nodeid, journaldate, | 778 print >>hyperdb.DEBUG, 'addjournal', (nodeid, journaldate, |
| 796 journaltag, action, params) | 779 journaltag, action, params) |
| 797 | 780 |
| 798 cursor = self.conn.cursor() | 781 self.save_journal(classname, cols, nodeid, journaldate, |
| 799 self.save_journal(cursor, classname, cols, nodeid, journaldate, | |
| 800 journaltag, action, params) | 782 journaltag, action, params) |
| 801 | 783 |
| 802 def save_journal(self, cursor, classname, cols, nodeid, journaldate, | 784 def save_journal(self, classname, cols, nodeid, journaldate, |
| 803 journaltag, action, params): | 785 journaltag, action, params): |
| 804 ''' Save the journal entry to the database | 786 ''' Save the journal entry to the database |
| 805 ''' | 787 ''' |
| 806 raise NotImplemented | 788 raise NotImplemented |
| 807 | 789 |
| 810 ''' | 792 ''' |
| 811 # make sure the node exists | 793 # make sure the node exists |
| 812 if not self.hasnode(classname, nodeid): | 794 if not self.hasnode(classname, nodeid): |
| 813 raise IndexError, '%s has no node %s'%(classname, nodeid) | 795 raise IndexError, '%s has no node %s'%(classname, nodeid) |
| 814 | 796 |
| 815 cursor = self.conn.cursor() | |
| 816 cols = ','.join('nodeid date tag action params'.split()) | 797 cols = ','.join('nodeid date tag action params'.split()) |
| 817 return self.load_journal(cursor, classname, cols, nodeid) | 798 return self.load_journal(classname, cols, nodeid) |
| 818 | 799 |
| 819 def load_journal(self, cursor, classname, cols, nodeid): | 800 def load_journal(self, classname, cols, nodeid): |
| 820 ''' Load the journal from the database | 801 ''' Load the journal from the database |
| 821 ''' | 802 ''' |
| 822 raise NotImplemented | 803 raise NotImplemented |
| 823 | 804 |
| 824 def pack(self, pack_before): | 805 def pack(self, pack_before): |
| 826 ''' | 807 ''' |
| 827 # get a 'yyyymmddhhmmss' version of the date | 808 # get a 'yyyymmddhhmmss' version of the date |
| 828 date_stamp = pack_before.serialise() | 809 date_stamp = pack_before.serialise() |
| 829 | 810 |
| 830 # do the delete | 811 # do the delete |
| 831 cursor = self.conn.cursor() | |
| 832 for classname in self.classes.keys(): | 812 for classname in self.classes.keys(): |
| 833 sql = "delete from %s__journal where date<%s and "\ | 813 sql = "delete from %s__journal where date<%s and "\ |
| 834 "action<>'create'"%(classname, self.arg) | 814 "action<>'create'"%(classname, self.arg) |
| 835 if __debug__: | 815 if __debug__: |
| 836 print >>hyperdb.DEBUG, 'pack', (self, sql, date_stamp) | 816 print >>hyperdb.DEBUG, 'pack', (self, sql, date_stamp) |
| 837 cursor.execute(sql, (date_stamp,)) | 817 self.cursor.execute(sql, (date_stamp,)) |
| 838 | 818 |
| 839 def sql_commit(self): | 819 def sql_commit(self): |
| 840 ''' Actually commit to the database. | 820 ''' Actually commit to the database. |
| 841 ''' | 821 ''' |
| 842 self.conn.commit() | 822 self.conn.commit() |
| 1468 methods, and other nodes may reuse the values of their key properties. | 1448 methods, and other nodes may reuse the values of their key properties. |
| 1469 ''' | 1449 ''' |
| 1470 if self.db.journaltag is None: | 1450 if self.db.journaltag is None: |
| 1471 raise DatabaseError, 'Database open read-only' | 1451 raise DatabaseError, 'Database open read-only' |
| 1472 | 1452 |
| 1473 cursor = self.db.conn.cursor() | |
| 1474 sql = 'update _%s set __retired__=1 where id=%s'%(self.classname, | 1453 sql = 'update _%s set __retired__=1 where id=%s'%(self.classname, |
| 1475 self.db.arg) | 1454 self.db.arg) |
| 1476 if __debug__: | 1455 if __debug__: |
| 1477 print >>hyperdb.DEBUG, 'retire', (self, sql, nodeid) | 1456 print >>hyperdb.DEBUG, 'retire', (self, sql, nodeid) |
| 1478 cursor.execute(sql, (nodeid,)) | 1457 self.db.cursor.execute(sql, (nodeid,)) |
| 1479 | 1458 |
| 1480 def is_retired(self, nodeid): | 1459 def is_retired(self, nodeid): |
| 1481 '''Return true if the node is rerired | 1460 '''Return true if the node is rerired |
| 1482 ''' | 1461 ''' |
| 1483 cursor = self.db.conn.cursor() | |
| 1484 sql = 'select __retired__ from _%s where id=%s'%(self.classname, | 1462 sql = 'select __retired__ from _%s where id=%s'%(self.classname, |
| 1485 self.db.arg) | 1463 self.db.arg) |
| 1486 if __debug__: | 1464 if __debug__: |
| 1487 print >>hyperdb.DEBUG, 'is_retired', (self, sql, nodeid) | 1465 print >>hyperdb.DEBUG, 'is_retired', (self, sql, nodeid) |
| 1488 cursor.execute(sql, (nodeid,)) | 1466 self.db.cursor.execute(sql, (nodeid,)) |
| 1489 return int(cursor.fetchone()[0]) | 1467 return int(self.db.sql_fetchone()[0]) |
| 1490 | 1468 |
| 1491 def destroy(self, nodeid): | 1469 def destroy(self, nodeid): |
| 1492 '''Destroy a node. | 1470 '''Destroy a node. |
| 1493 | 1471 |
| 1494 WARNING: this method should never be used except in extremely rare | 1472 WARNING: this method should never be used except in extremely rare |
| 1582 otherwise a KeyError is raised. | 1560 otherwise a KeyError is raised. |
| 1583 ''' | 1561 ''' |
| 1584 if not self.key: | 1562 if not self.key: |
| 1585 raise TypeError, 'No key property set for class %s'%self.classname | 1563 raise TypeError, 'No key property set for class %s'%self.classname |
| 1586 | 1564 |
| 1587 cursor = self.db.conn.cursor() | |
| 1588 sql = 'select id,__retired__ from _%s where _%s=%s'%(self.classname, | 1565 sql = 'select id,__retired__ from _%s where _%s=%s'%(self.classname, |
| 1589 self.key, self.db.arg) | 1566 self.key, self.db.arg) |
| 1590 self.db.sql(cursor, sql, (keyvalue,)) | 1567 self.db.sql(sql, (keyvalue,)) |
| 1591 | 1568 |
| 1592 # see if there was a result that's not retired | 1569 # see if there was a result that's not retired |
| 1593 l = cursor.fetchall() | 1570 l = self.db.cursor.fetchall() |
| 1594 if not l or int(l[0][1]): | 1571 if not l or int(l[0][1]): |
| 1595 raise KeyError, 'No key (%s) value "%s" for "%s"'%(self.key, | 1572 raise KeyError, 'No key (%s) value "%s" for "%s"'%(self.key, |
| 1596 keyvalue, self.classname) | 1573 keyvalue, self.classname) |
| 1597 | 1574 |
| 1598 # return the id | 1575 # return the id |
| 1624 allvalues += tuple(values.keys()) | 1601 allvalues += tuple(values.keys()) |
| 1625 a = self.db.arg | 1602 a = self.db.arg |
| 1626 tables.append('select nodeid from %s_%s where linkid in (%s)'%( | 1603 tables.append('select nodeid from %s_%s where linkid in (%s)'%( |
| 1627 self.classname, prop, ','.join([a for x in values.keys()]))) | 1604 self.classname, prop, ','.join([a for x in values.keys()]))) |
| 1628 sql = '\nintersect\n'.join(tables) | 1605 sql = '\nintersect\n'.join(tables) |
| 1629 if __debug__: | 1606 self.db.sql(sql, allvalues) |
| 1630 print >>hyperdb.DEBUG, 'find', (self, sql, allvalues) | |
| 1631 cursor = self.db.conn.cursor() | |
| 1632 cursor.execute(sql, allvalues) | |
| 1633 try: | 1607 try: |
| 1634 l = [x[0] for x in cursor.fetchall()] | 1608 l = [x[0] for x in self.db.cursor.fetchall()] |
| 1635 except gadfly.database.error, message: | 1609 except gadfly.database.error, message: |
| 1636 if message == 'no more results': | 1610 if message == 'no more results': |
| 1637 l = [] | 1611 l = [] |
| 1638 raise | 1612 raise |
| 1639 if __debug__: | 1613 if __debug__: |
| 1770 cols = ','.join(cols) | 1744 cols = ','.join(cols) |
| 1771 sql = 'select %s from %s %s%s%s'%(cols, frum, where, group, order) | 1745 sql = 'select %s from %s %s%s%s'%(cols, frum, where, group, order) |
| 1772 args = tuple(args) | 1746 args = tuple(args) |
| 1773 if __debug__: | 1747 if __debug__: |
| 1774 print >>hyperdb.DEBUG, 'filter', (self, sql, args) | 1748 print >>hyperdb.DEBUG, 'filter', (self, sql, args) |
| 1775 cursor = self.db.conn.cursor() | 1749 self.db.cursor.execute(sql, args) |
| 1776 cursor.execute(sql, args) | 1750 l = self.db.cursor.fetchall() |
| 1777 l = cursor.fetchall() | |
| 1778 | 1751 |
| 1779 # return the IDs (the first column) | 1752 # return the IDs (the first column) |
| 1780 # XXX The filter(None, l) bit is sqlite-specific... if there's _NO_ | 1753 # XXX The filter(None, l) bit is sqlite-specific... if there's _NO_ |
| 1781 # XXX matches to a fetch, it returns NULL instead of nothing!?! | 1754 # XXX matches to a fetch, it returns NULL instead of nothing!?! |
| 1782 return filter(None, [row[0] for row in l]) | 1755 return filter(None, [row[0] for row in l]) |
