Parent Directory
|
Revision Log
Fixed (again) the GlobalTableEmulator. -sam
1 ################################################################################ 2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project 3 # $Id$ 4 ################################################################################ 5 6 package WeBWorK::DB; 7 8 =head1 NAME 9 10 WeBWorK::DB - interface with the WeBWorK databases. 11 12 =head1 SYNOPSIS 13 14 my $db = WeBWorK::DB->new($courseEnvironment); 15 16 my @userIDs = $db->listUsers(); 17 my $Sam = $db->{user}->{record}->new(); 18 19 $Sam->user_id("sammy"); 20 $Sam->first_name("Sam"); 21 $Sam->last_name("Hathaway"); 22 # etc. 23 24 $db->addUser($User); 25 my $Dennis = $db->getUser("dennis"); 26 $Dennis->status("C"); 27 $db->putUser->($Dennis); 28 29 $db->deleteUser("sammy"); 30 31 =head1 DESCRIPTION 32 33 WeBWorK::DB provides a consistent interface to a number of database backends. 34 Access and modification functions are provided for each logical table used by 35 the webwork system. The particular backend ("schema" and "driver"), record 36 class, data source, and additional parameters are specified by the C<%dbLayout> 37 hash in the course environment. 38 39 =head1 ARCHITECTURE 40 41 The new database system uses a three-tier architecture to insulate each layer 42 from the adjacent layers. 43 44 =head2 Top Layer: DB 45 46 The top layer of the architecture is the DB module. It provides the methods 47 listed below, and uses schema modules (via tables) to implement those methods. 48 49 / list* exists* add* get* put* delete* \ <- api 50 +------------------------------------------------------------------+ 51 | DB | 52 +------------------------------------------------------------------+ 53 \ password permission key user set set_user problem problem_user / <- tables 54 55 =head2 Middle Layer: Schemas 56 57 The middle layer of the architecture is provided by one or more schema modules. 58 They are called "schema" modules because they control the structure of the data 59 for a table. This includes odd things like the way multiple tables are encoded 60 in a single hash in the WW1Hash schema, and the encoding scheme used. 61 62 The schema modules provide an API that matches the requirements of the DB 63 layer, on a per-table basis. Each schema module has a style that determines 64 which drivers it can interface with. For example, WW1Hash is a "hash" style 65 schema. SQL is a "dbi" style schema. 66 67 =head3 Examples 68 69 Both WeBWorK 1.x and 2.x courses use: 70 71 / password permission key \ / user \ <- tables provided 72 +-----------------------------+ +----------------+ 73 | Auth1Hash | | Classlist1Hash | 74 +-----------------------------+ +----------------+ 75 \ hash / \ hash / <- driver style required 76 77 WeBWorK 1.x courses also use: 78 79 / set_user problem_user \ / set problem \ 80 +-------------------------+ +---------------------+ 81 | WW1Hash | | GlobalTableEmulator | 82 +-------------------------+ +---------------------+ 83 \ hash / \ null / 84 85 The GlobalTableEmulator schema emulates the global set and problem tables using 86 data from the set_user and problem_user tables. 87 88 WeBWorK 2.x courses also use: 89 90 / set set_user problem problem_user \ 91 +-------------------------------------+ 92 | WW2Hash | 93 +-------------------------------------+ 94 \ hash / 95 96 =head2 Bottom Layer: Drivers 97 98 Driver modules implement a style for a schema. They provide physical access to 99 a data source containing the data for a table. The style of a driver determines 100 what methods it provides. All drivers provide C<connect(MODE)> and 101 C<disconnect()> methods. A hash style driver provides a C<hash()> method which 102 returns the tied hash. A dbi style driver provides a C<handle()> method which 103 returns the DBI handle. 104 105 =head3 Examples 106 107 / hash \ / hash \ / hash \ <- style 108 +--------+ +--------+ +--------+ 109 | DB | | GDBM | | DB3 | 110 +--------+ +--------+ +--------+ 111 112 / dbi \ / ldap \ 113 +-------+ +--------+ 114 | SQL | | LDAP | 115 +-------+ +--------+ 116 117 =head2 Record Types 118 119 In C<%dblayout>, each table is assigned a record class, used for passing 120 complete records to and from the database. The default record classes are 121 subclasses of the WeBWorK::DB::Record class, and are named as follows: User, 122 Password, PermissionLevel, Key, Set, UserSet, Problem, UserProblem. In the 123 following documentation, a reference the the record class for a table means the 124 record class currently defined for that table in C<%dbLayout>. 125 126 =cut 127 128 use strict; 129 use warnings; 130 use Data::Dumper; 131 use WeBWorK::Utils qw(runtime_use); 132 133 use constant TABLES => qw(password permission key user set set_user problem problem_user); 134 135 ################################################################################ 136 # constructor 137 ################################################################################ 138 139 =head1 CONSTRUCTOR 140 141 =over 142 143 =item new($ce) 144 145 The C<new> method creates a DB object and brings up the underlying 146 schema/driver structure according to the C<%dbLayout> hash in C<$ce>, a 147 WeBWorK::CourseEnvironment object. 148 149 =back 150 151 =cut 152 153 sub new($$) { 154 my ($invocant, $ce) = @_; 155 my $class = ref($invocant) || $invocant; 156 my $self = {}; 157 bless $self, $class; # bless this here so we can pass it to the schema 158 159 # load the modules required to handle each table, and create driver 160 foreach my $table (TABLES) { 161 unless (defined $ce->{dbLayout}->{$table}) { 162 warn "ignoring table $table: layout not specified in dbLayout"; # *** 163 next; 164 } 165 166 my $layout = $ce->{dbLayout}->{$table}; 167 my $record = $layout->{record}; 168 my $schema = $layout->{schema}; 169 my $driver = $layout->{driver}; 170 my $source = $layout->{source}; 171 my $params = $layout->{params}; 172 173 runtime_use($record); 174 runtime_use($schema); 175 runtime_use($driver); 176 $self->{$table} = $schema->new( 177 $self, 178 $driver->new($source, $params), 179 $table, 180 $record, 181 $params 182 ); 183 } 184 185 return $self; 186 } 187 188 =head1 METHODS 189 190 =cut 191 192 ################################################################################ 193 # password functions 194 ################################################################################ 195 196 =head2 Password Methods 197 198 =over 199 200 =item listPasswords() 201 202 Returns a list of user IDs representing the records in the password table. 203 204 =cut 205 206 sub listPasswords($) { 207 my ($self) = @_; 208 return map { $_->[0] } 209 $self->{password}->list(undef); 210 } 211 212 =item addPassword($Password) 213 214 $Password is a record object. The password will be added to the password table 215 if a password with the same user ID does not already exist. If one does exist, 216 an exception is thrown. To add a password, a user with a matching user ID must 217 exist in the user table. 218 219 =cut 220 221 sub addPassword($$) { 222 my ($self, $Password) = @_; 223 die __PACKAGE__, ": addPassword($Password) failed: user not found.\n" 224 unless $self->{user}->exists($Password->user_id); 225 return $self->{password}->add($Password); 226 } 227 228 =item getPassword($userID) 229 230 If a record with a matching user ID exists, a record object containting that 231 record's data will be returned. If no such record exists, an undefined value 232 will be returned. 233 234 =cut 235 236 sub getPassword($$) { 237 my ($self, $userID) = @_; 238 die __PACKAGE__, ": getPassword() failed: you must specify a userID.\n" 239 unless $userID; 240 return $self->{password}->get($userID); 241 } 242 243 =item putPassword($Password) 244 245 $Password is a record object. If a password record with the same user ID exists 246 in the password table, the data in the record is replaced with the data in 247 $Password. If a matching password record does not exist, an exception is 248 thrown. 249 250 =cut 251 252 sub putPassword($$) { 253 my ($self, $Password) = @_; 254 return $self->{password}->put($Password); 255 } 256 257 =item deletePassword($userID) 258 259 If a password record with a user ID matching $userID exists in the password 260 table, it is removed and the method returns a true value. If one does exist, 261 a false value is returned. 262 263 =cut 264 265 sub deletePassword($$) { 266 my ($self, $userID) = @_; 267 return $self->{password}->delete($userID); 268 } 269 270 =back 271 272 =cut 273 274 ################################################################################ 275 # permission functions 276 ################################################################################ 277 278 sub listPermissionLevels($) { 279 my ($self) = @_; 280 return map { $_->[0] } 281 $self->{permission}->list(undef); 282 } 283 284 sub addPermissionLevel($$) { 285 my ($self, $PermissionLevel) = @_; 286 die "addPermissionLevel failed: user ", $PermissionLevel->user_id, " does not exist.\n" 287 unless $self->{user}->exists($PermissionLevel->user_id); 288 return $self->{permission}->add($PermissionLevel); 289 } 290 291 sub getPermissionLevel($$) { 292 my ($self, $userID) = @_; 293 return $self->{permission}->get($userID); 294 } 295 296 sub putPermissionLevel($$) { 297 my ($self, $PermissionLevel) = @_; 298 return $self->{permission}->put($PermissionLevel); 299 } 300 301 sub deletePermissionLevel($$) { 302 my ($self, $userID) = @_; 303 return $self->{permission}->delete($userID); 304 } 305 306 ################################################################################ 307 # key functions 308 ################################################################################ 309 310 sub listKeys($) { 311 my ($self) = @_; 312 return map { $_->[0] } 313 $self->{key}->list(undef); 314 } 315 316 sub addKey($$) { 317 my ($self, $Key) = @_; 318 die "addKey failed: user ", $Key->user_id, " does not exist.\n" 319 unless $self->{user}->exists($Key->user_id); 320 return $self->{key}->add($Key); 321 } 322 323 sub getKey($$) { 324 my ($self, $userID) = @_; 325 return $self->{key}->get($userID); 326 } 327 328 sub putKey($$) { 329 my ($self, $Key) = @_; 330 return $self->{key}->put($Key); 331 } 332 333 sub deleteKey($$) { 334 my ($self, $userID) = @_; 335 return $self->{key}->delete($userID); 336 } 337 338 ################################################################################ 339 # user functions 340 ################################################################################ 341 342 sub listUsers($) { 343 my ($self) = @_; 344 return map { $_->[0] } 345 $self->{user}->list(undef); 346 } 347 348 sub addUser($$) { 349 my ($self, $User) = @_; 350 return $self->{user}->add($User); 351 } 352 353 sub getUser($$) { 354 my ($self, $userID) = @_; 355 return $self->{user}->get($userID); 356 } 357 358 sub putUser($$) { 359 my ($self, $User) = @_; 360 return $self->{user}->put($User); 361 } 362 363 sub deleteUser($$) { 364 my ($self, $userID) = @_; 365 $self->deletePassword($userID); 366 $self->deletePermissionLevel($userID); 367 $self->deleteKey($userID); 368 $self->deleteUserSet($userID, $_) 369 foreach $self->listUsers(); 370 return $self->{user}->delete($userID); 371 } 372 373 ################################################################################ 374 # set functions 375 ################################################################################ 376 377 sub listGlobalSets($) { 378 my ($self) = @_; 379 return map { $_->[0] } 380 $self->{set}->list(undef); 381 } 382 383 sub addGlobalSet($$) { 384 my ($self, $GlobalSet) = @_; 385 return $self->{set}->add($GlobalSet); 386 } 387 388 sub getGlobalSet($$) { 389 my ($self, $setID) = @_; 390 return $self->{set}->get($setID); 391 } 392 393 sub putGlobalSet($$) { 394 my ($self, $GlobalSet) = @_; 395 return $self->{set}->put($GlobalSet); 396 } 397 398 sub deleteGlobalSet($$) { 399 my ($self, $setID) = @_; 400 $self->deleteGlobalProblem($setID, $_) 401 foreach $self->listGlobalProblems($setID); 402 $self->deleteUserSet($_, $setID) 403 foreach $self->listUsers(); 404 return $self->{set}->delete($setID); 405 } 406 407 ################################################################################ 408 # set_user functions 409 ################################################################################ 410 411 sub listSetUsers($$) { 412 my ($self, $setID) = @_; 413 return map { $_->[0] } # extract user_id 414 $self->{set_user}->list(undef, $setID); 415 } 416 417 sub listUserSets($$) { 418 my ($self, $userID) = @_; 419 return map { $_->[1] } # extract set_id 420 $self->{set_user}->list($userID, undef); 421 } 422 423 sub addUserSet($$) { 424 my ($self, $UserSet) = @_; 425 die "addUserSet failed: user ", $UserSet->user_id, " does not exist.\n" 426 unless $self->{user}->exists($UserSet->user_id); 427 die "addUserSet failed: set ", $UserSet->set_id, " does not exist.\n" 428 unless $self->{set}->exists($UserSet->set_id); 429 return $self->{set_user}->add($UserSet); 430 } 431 432 sub getUserSet($$$) { 433 my ($self, $userID, $setID) = @_; 434 return $self->{set_user}->get($userID, $setID); 435 } 436 437 sub putUserSet($$) { 438 my ($self, $UserSet) = @_; 439 return $self->{set_user}->put($UserSet); 440 } 441 442 sub deleteUserSet($$$) { 443 my ($self, $userID, $setID) = @_; 444 $self->deleteUserProblem($userID, $setID, $_) 445 foreach $self->listUserProblems($userID, $setID); 446 return $self->{set_user}->delete($userID, $setID); 447 } 448 449 ################################################################################ 450 # problem functions 451 ################################################################################ 452 453 sub listGlobalProblems($$) { 454 my ($self, $setID) = @_; 455 return map { $_->[1] } 456 #grep { $_->[0] eq $setID } 457 $self->{problem}->list($setID, undef); 458 } 459 460 sub addGlobalProblem($$) { 461 my ($self, $GlobalProblem) = @_; 462 die "addGlobalProblem failed: set ", $GlobalProblem->set_id, " does not exist.\n" 463 unless $self->{set}->exists($GlobalProblem->set_id); 464 return $self->{problem}->add($GlobalProblem); 465 } 466 467 sub getGlobalProblem($$$) { 468 my ($self, $setID, $problemID) = @_; 469 return $self->{problem}->get($setID, $problemID); 470 } 471 472 sub putGlobalProblem($$) { 473 my ($self, $GlobalProblem) = @_; 474 return $self->{problem}->put($GlobalProblem); 475 } 476 477 sub deleteGlobalProblem($$$) { 478 my ($self, $setID, $problemID) = @_; 479 $self->deleteUserProblem($_, $setID, $problemID) 480 foreach $self->listUsers(); 481 return $self->{problem}->delete($setID, $problemID); 482 } 483 484 ################################################################################ 485 # problem_user functions 486 ################################################################################ 487 488 sub listProblemUsers($$$) { 489 my ($self, $setID, $problemID) = @_; 490 return map { $_->[0] } # extract user_id 491 $self->{problem_user}->list(undef, $setID, $problemID); 492 } 493 494 sub listUserProblems($$$) { 495 my ($self, $userID, $setID) = @_; 496 return map { $_->[2] } # extract problem_id 497 $self->{problem_user}->list($userID, $setID, undef); 498 } 499 500 sub addUserProblem($$) { 501 my ($self, $UserProblem) = @_; 502 die "addUserProblem failed: user set ", $UserProblem->set_id, " does not exist.\n" 503 unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id); 504 die "addUserProblem failed: problem ", $UserProblem->problem_id, " does not exist.\n" 505 unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id); 506 return $self->{problem_user}->add($UserProblem); 507 } 508 509 sub getUserProblem($$$$) { 510 my ($self, $userID, $setID, $problemID) = @_; 511 return $self->{problem_user}->get($userID, $setID, $problemID); 512 } 513 514 sub putUserProblem($$) { 515 my ($self, $UserProblem) = @_; 516 return $self->{problem_user}->put($UserProblem); 517 } 518 519 sub deleteUserProblem($$$$) { 520 my ($self, $userID, $setID, $problemID) = @_; 521 return $self->{problem_user}->delete($userID, $setID, $problemID); 522 } 523 524 ################################################################################ 525 # set+set_user functions 526 ################################################################################ 527 528 sub getGlobalUserSet($$$) { 529 my ($self, $userID, $setID) = @_; 530 my $UserSet = $self->getUserSet($userID, $setID); 531 return unless $UserSet; 532 my $GlobalSet = $self->getGlobalSet($setID); 533 if ($GlobalSet) { 534 foreach ($UserSet->FIELDS()) { 535 next unless $GlobalSet->can($_); 536 next if $UserSet->$_(); 537 $UserSet->$_($GlobalSet->$_()); 538 } 539 } 540 return $UserSet; 541 } 542 543 ################################################################################ 544 # problem+problem_user functions 545 ################################################################################ 546 547 sub getGlobalUserProblem($$$$) { 548 my ($self, $userID, $setID, $problemID) = @_; 549 my $UserProblem = $self->getUserProblem($userID, $setID, $problemID); 550 return unless $UserProblem; 551 my $GlobalProblem = $self->getGlobalProblem($setID, $problemID); 552 if ($GlobalProblem) { 553 foreach ($UserProblem->FIELDS()) { 554 next unless $GlobalProblem->can($_); 555 next if $UserProblem->$_(); 556 $UserProblem->$_($GlobalProblem->$_()); 557 } 558 } 559 return $UserProblem; 560 } 561 562 ################################################################################ 563 # enforcement 564 ################################################################################ 565 566 ################################################################################ 567 # debugging 568 ################################################################################ 569 570 sub dumpDB($$) { 571 my ($self, $table) = @_; 572 return $self->{$table}->dumpDB(); 573 } 574 575 =head1 AUTHOR 576 577 Written by Sam Hathaway, sh002i (at) math.rochester.edu. 578 579 =cut 580 581 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |