[system] / trunk / webwork-modperl / lib / WeBWorK / DB.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/DB.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1583 - (download) (as text) (annotate)
Tue Oct 14 18:02:39 2003 UTC (9 years, 7 months ago) by sh002i
File size: 44598 byte(s)
closes bug #251: getMergedSets and getMergedProblems now call
getGlobalSets/getUserSets and getGlobalProblems/getUserProblems.

The algorithm used to merge fields is also more efficient now, and
getMergedSet/getMergedProblem have been reimplemented to call
getMergedSets/getMergedProblems.

    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          / new* list* exists* add* get* get*s 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 Carp;
  131 use Data::Dumper;
  132 use WeBWorK::Timing;
  133 use WeBWorK::Utils qw(runtime_use);
  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 =head2 C<%dbLayout> Format
  152 
  153 The C<%dbLayout> hash consists of items keyed by table names. The value of each
  154 item is a reference to a hash containing the following items:
  155 
  156 =over
  157 
  158 =item record
  159 
  160 The name of a perl module to use for representing the data in a record.
  161 
  162 =item schema
  163 
  164 The name of a perl module to use for access to the table.
  165 
  166 =item driver
  167 
  168 The name of a perl module to use for access to the data source.
  169 
  170 =item source
  171 
  172 The location of the data source that should be used by the driver module.
  173 Depending on the driver, this may be a path, a url, or a DBI spec.
  174 
  175 =item params
  176 
  177 A reference to a hash containing extra information needed by the schema. Some
  178 schemas require parameters, some do not. Consult the documentation for the
  179 schema in question.
  180 
  181 =back
  182 
  183 For each table defined in C<%dbLayout>, C<new> loads the record, schema, and
  184 driver modules. It the schema module's C<tables> method lists the current table
  185 (or contains the string "*") and the output of the schema and driver modules'
  186 C<style> methods match, the table is installed. Otherwise, an exception is
  187 thrown.
  188 
  189 =cut
  190 
  191 sub new($$) {
  192   my ($invocant, $ce) = @_;
  193   my $class = ref($invocant) || $invocant;
  194   my $self = {};
  195   bless $self, $class; # bless this here so we can pass it to the schema
  196 
  197   # load the modules required to handle each table, and create driver
  198   my %dbLayout = %{$ce->{dbLayout}};
  199   foreach my $table (keys %dbLayout) {
  200     my $layout = $dbLayout{$table};
  201     my $record = $layout->{record};
  202     my $schema = $layout->{schema};
  203     my $driver = $layout->{driver};
  204     my $source = $layout->{source};
  205     my $params = $layout->{params};
  206 
  207     runtime_use($record);
  208 
  209     runtime_use($driver);
  210     my $driverObject = eval { $driver->new($source, $params) };
  211     croak "error instantiating DB driver $driver for table $table: $@"
  212       if $@;
  213 
  214     runtime_use($schema);
  215     my $schemaObject = eval { $schema->new(
  216       $self, $driver->new($source, $params),
  217       $table, $record, $params) };
  218     croak "error instantiating DB schema $schema for table $table: $@"
  219       if $@;
  220 
  221     $self->{$table} = $schemaObject;
  222   }
  223 
  224   return $self;
  225 }
  226 
  227 =head1 METHODS
  228 
  229 =cut
  230 
  231 ################################################################################
  232 # password functions
  233 ################################################################################
  234 
  235 =head2 Password Methods
  236 
  237 =over
  238 
  239 =item newPassword()
  240 
  241 Returns a new, empty password object.
  242 
  243 =cut
  244 
  245 sub newPassword {
  246   my ($self, $prototype) = @_;
  247   return $self->{password}->{record}->new($prototype);
  248 }
  249 
  250 =item listPasswords()
  251 
  252 Returns a list of user IDs representing the records in the password table.
  253 
  254 =cut
  255 
  256 sub listPasswords {
  257   my ($self) = @_;
  258 
  259   croak "listPasswords: requires 0 arguments"
  260     unless @_ == 1;
  261 
  262   return map { $_->[0] }
  263     $self->{password}->list(undef);
  264 }
  265 
  266 =item addPassword($Password)
  267 
  268 $Password is a record object. The password will be added to the password table
  269 if a password with the same user ID does not already exist. If one does exist,
  270 an exception is thrown. To add a password, a user with a matching user ID must
  271 exist in the user table.
  272 
  273 =cut
  274 
  275 sub addPassword {
  276   my ($self, $Password) = @_;
  277 
  278   croak "addPassword: requires 1 argument"
  279     unless @_ == 2;
  280   croak "addPassword: argument 1 must be of type ", $self->{password}->{record}
  281     unless ref $Password eq $self->{password}->{record};
  282   croak "addPassword: password exists (perhaps you meant to use putPassword?)"
  283     if $self->{password}->exists($Password->user_id);
  284   croak "addPassword: user ", $Password->user_id, " not found"
  285     unless $self->{user}->exists($Password->user_id);
  286 
  287   checkKeyfields($Password);
  288 
  289   return $self->{password}->add($Password);
  290 }
  291 
  292 =item getPassword($userID)
  293 
  294 If a record with a matching user ID exists, a record object containting that
  295 record's data will be returned. If no such record exists, an undefined value
  296 will be returned.
  297 
  298 =cut
  299 
  300 sub getPassword {
  301   my ($self, $userID) = @_;
  302 
  303   croak "getPassword: requires 1 argument"
  304     unless @_ == 2;
  305   croak "getPassword: argument 1 must contain a user_id"
  306     unless defined $userID;
  307 
  308   return $self->{password}->get($userID);
  309 }
  310 
  311 =item getPasswords(@uesrIDs)
  312 
  313 Return a list of password records associated with the user IDs given. If there
  314 is no record associated with a given user ID, that element of the list will be
  315 undefined.
  316 
  317 =cut
  318 
  319 sub getPasswords {
  320   my ($self, @userIDs) = @_;
  321 
  322   croak "getPasswords: requires 1 or more argument"
  323     unless @_ >= 2;
  324   foreach my $i (0 .. $#userIDs) {
  325     croak "getPasswords: element $i of argument list must contain a user_id"
  326       unless defined $userIDs[$i];
  327   }
  328 
  329   return $self->{password}->gets(@userIDs);
  330 }
  331 
  332 =item putPassword($Password)
  333 
  334 $Password is a record object. If a password record with the same user ID exists
  335 in the password table, the data in the record is replaced with the data in
  336 $Password. If a matching password record does not exist, an exception is
  337 thrown.
  338 
  339 =cut
  340 
  341 sub putPassword($$) {
  342   my ($self, $Password) = @_;
  343 
  344   croak "putPassword: requires 1 argument"
  345     unless @_ == 2;
  346   croak "putPassword: argument 1 must be of type ", $self->{password}->{record}
  347     unless ref $Password eq $self->{password}->{record};
  348   croak "putPassword: password not found (perhaps you meant to use addPassword?)"
  349     unless $self->{password}->exists($Password->user_id);
  350 
  351   checkKeyfields($Password);
  352 
  353   return $self->{password}->put($Password);
  354 }
  355 
  356 =item deletePassword($userID)
  357 
  358 If a password record with a user ID matching $userID exists in the password
  359 table, it is removed and the method returns a true value. If one does exist,
  360 a false value is returned.
  361 
  362 =cut
  363 
  364 sub deletePassword($$) {
  365   my ($self, $userID) = @_;
  366 
  367   croak "putPassword: requires 1 argument"
  368     unless @_ == 2;
  369   croak "deletePassword: argument 1 must contain a user_id"
  370     unless defined $userID;
  371 
  372   return $self->{password}->delete($userID);
  373 }
  374 
  375 =back
  376 
  377 =cut
  378 
  379 ################################################################################
  380 # permission functions
  381 ################################################################################
  382 
  383 =head2 Permission Level Methods
  384 
  385 =over
  386 
  387 =item newPermissionLevel()
  388 
  389 Returns a new, empty permission level object.
  390 
  391 =cut
  392 
  393 sub newPermissionLevel {
  394   my ($self, $prototype) = @_;
  395   return $self->{permission}->{record}->new($prototype);
  396 }
  397 
  398 =item listPermissionLevels()
  399 
  400 Returns a list of user IDs representing the records in the permission table.
  401 
  402 =cut
  403 
  404 sub listPermissionLevels($) {
  405   my ($self) = @_;
  406 
  407   croak "listPermissionLevels: requires 0 arguments"
  408     unless @_ == 1;
  409 
  410   return map { $_->[0] }
  411     $self->{permission}->list(undef);
  412 }
  413 
  414 =item addPermissionLevel($PermissionLevel)
  415 
  416 $PermissionLevel is a record object. The permission level will be added to the
  417 permission table if a permission level with the same user ID does not already
  418 exist. If one does exist, an exception is thrown. To add a permission level, a
  419 user with a matching user ID must exist in the user table.
  420 
  421 =cut
  422 
  423 sub addPermissionLevel($$) {
  424   my ($self, $PermissionLevel) = @_;
  425 
  426   croak "addPermissionLevel: requires 1 argument"
  427     unless @_ == 2;
  428   croak "addPermissionLevel: argument 1 must be of type ", $self->{permission}->{record}
  429     unless ref $PermissionLevel eq $self->{permission}->{record};
  430   croak "addPermissionLevel: permission level exists (perhaps you meant to use putPermissionLevel?)"
  431     if $self->{permission}->exists($PermissionLevel->user_id);
  432   croak "addPermissionLevel: user ", $PermissionLevel->user_id, " not found"
  433     unless $self->{user}->exists($PermissionLevel->user_id);
  434 
  435   checkKeyfields($PermissionLevel);
  436 
  437   return $self->{permission}->add($PermissionLevel);
  438 }
  439 
  440 =item getPermissionLevel($userID)
  441 
  442 If a record with a matching user ID exists, a record object containting that
  443 record's data will be returned. If no such record exists, an undefined value
  444 will be returned.
  445 
  446 =cut
  447 
  448 sub getPermissionLevel($$) {
  449   my ($self, $userID) = @_;
  450 
  451   croak "getPermissionLevel: requires 1 argument"
  452     unless @_ == 2;
  453   croak "getPermissionLevel: argument 1 must contain a user_id"
  454     unless defined $userID;
  455 
  456   return $self->{permission}->get($userID);
  457 }
  458 
  459 =item getPermissionLevels(@uesrIDs)
  460 
  461 Return a list of permission level records associated with the user IDs given. If
  462 there is no record associated with a given user ID, that element of the list
  463 will be undefined.
  464 
  465 =cut
  466 
  467 sub getPermissionLevels {
  468   my ($self, @userIDs) = @_;
  469 
  470   croak "getPermissionLevels: requires 1 or more argument"
  471     unless @_ >= 2;
  472   foreach my $i (0 .. $#userIDs) {
  473     croak "getPermissionLevels: element $i of argument list must contain a user_id"
  474       unless defined $userIDs[$i];
  475   }
  476 
  477   return $self->{permission}->gets(@userIDs);
  478 }
  479 
  480 =item putPermissionLevel($PermissionLevel)
  481 
  482 $PermissionLevel is a record object. If a permission level record with the same
  483 user ID exists in the permission table, the data in the record is replaced with
  484 the data in $PermissionLevel. If a matching permission level record does not
  485 exist, an exception is thrown.
  486 
  487 =cut
  488 
  489 sub putPermissionLevel($$) {
  490   my ($self, $PermissionLevel) = @_;
  491 
  492   croak "putPermissionLevel: requires 1 argument"
  493     unless @_ == 2;
  494   croak "putPermissionLevel: argument 1 must be of type ", $self->{permission}->{record}
  495     unless ref $PermissionLevel eq $self->{permission}->{record};
  496   croak "putPermissionLevel: permission level not found (perhaps you meant to use addPermissionLevel?)"
  497     unless $self->{permission}->exists($PermissionLevel->user_id);
  498 
  499   checkKeyfields($PermissionLevel);
  500 
  501   return $self->{permission}->put($PermissionLevel);
  502 }
  503 
  504 =item deletePermissionLevel($userID)
  505 
  506 If a permission level record with a user ID matching $userID exists in the
  507 permission table, it is removed and the method returns a true value. If one
  508 does exist, a false value is returned.
  509 
  510 =cut
  511 
  512 sub deletePermissionLevel($$) {
  513   my ($self, $userID) = @_;
  514 
  515   croak "deletePermissionLevel: requires 1 argument"
  516     unless @_ == 2;
  517   croak "deletePermissionLevel: argument 1 must contain a user_id"
  518     unless defined $userID;
  519 
  520   return $self->{permission}->delete($userID);
  521 }
  522 
  523 ################################################################################
  524 # key functions
  525 ################################################################################
  526 
  527 =head2 Key Methods
  528 
  529 =over
  530 
  531 =item newKey()
  532 
  533 Returns a new, empty key object.
  534 
  535 =cut
  536 
  537 sub newKey {
  538   my ($self, $prototype) = @_;
  539   return $self->{key}->{record}->new($prototype);
  540 }
  541 
  542 =item listKeys()
  543 
  544 Returns a list of user IDs representing the records in the key table.
  545 
  546 =cut
  547 
  548 sub listKeys($) {
  549   my ($self) = @_;
  550 
  551   croak "listKeys: requires 0 arguments"
  552     unless @_ == 1;
  553 
  554   return map { $_->[0] }
  555     $self->{key}->list(undef);
  556 }
  557 
  558 =item addKey($Key)
  559 
  560 $Key is a record object. The key will be added to the key table if a key with
  561 the same user ID does not already exist. If one does exist, an exception is
  562 thrown. To add a key, a user with a matching user ID must exist in the user
  563 table.
  564 
  565 =cut
  566 
  567 sub addKey($$) {
  568   my ($self, $Key) = @_;
  569 
  570   croak "addKey: requires 1 argument"
  571     unless @_ == 2;
  572   croak "addKey: argument 1 must be of type ", $self->{key}->{record}
  573     unless ref $Key eq $self->{key}->{record};
  574   croak "addKey: key exists (perhaps you meant to use putKey?)"
  575     if $self->{key}->exists($Key->user_id);
  576   croak "addKey: user ", $Key->user_id, " not found"
  577     unless $self->{user}->exists($Key->user_id);
  578 
  579   checkKeyfields($Key);
  580 
  581   return $self->{key}->add($Key);
  582 }
  583 
  584 =item getKey($userID)
  585 
  586 If a record with a matching user ID exists, a record object containting that
  587 record's data will be returned. If no such record exists, an undefined value
  588 will be returned.
  589 
  590 =cut
  591 
  592 sub getKey($$) {
  593   my ($self, $userID) = @_;
  594 
  595   croak "getKey: requires 1 argument"
  596     unless @_ == 2;
  597   croak "getKey: argument 1 must contain a user_id"
  598     unless defined $userID;
  599 
  600   return $self->{key}->get($userID);
  601 }
  602 
  603 =item getKeys(@uesrIDs)
  604 
  605 Return a list of key records associated with the user IDs given. If there is no
  606 record associated with a given user ID, that element of the list will be
  607 undefined.
  608 
  609 =cut
  610 
  611 sub getKeys {
  612   my ($self, @userIDs) = @_;
  613 
  614   croak "getKeys: requires 1 or more argument"
  615     unless @_ >= 2;
  616   foreach my $i (0 .. $#userIDs) {
  617     croak "getKeys: element $i of argument list must contain a user_id"
  618       unless defined $userIDs[$i];
  619   }
  620 
  621   return $self->{key}->gets(@userIDs);
  622 }
  623 
  624 =item putKey($Key)
  625 
  626 $Key is a record object. If a key record with the same user ID exists in the
  627 key table, the data in the record is replaced with the data in $Key. If a
  628 matching key record does not exist, an exception is thrown.
  629 
  630 =cut
  631 
  632 sub putKey($$) {
  633   my ($self, $Key) = @_;
  634 
  635   croak "putKey: requires 1 argument"
  636     unless @_ == 2;
  637   croak "putKey: argument 1 must be of type ", $self->{key}->{record}
  638     unless ref $Key eq $self->{key}->{record};
  639   croak "putKey: key not found (perhaps you meant to use addKey?)"
  640     unless $self->{key}->exists($Key->user_id);
  641 
  642   checkKeyfields($Key);
  643 
  644   return $self->{key}->put($Key);
  645 }
  646 
  647 =item deleteKey($userID)
  648 
  649 If a key record with a user ID matching $userID exists in the key table, it is
  650 removed and the method returns a true value. If one does exist, a false value
  651 is returned.
  652 
  653 =cut
  654 
  655 sub deleteKey($$) {
  656   my ($self, $userID) = @_;
  657 
  658   croak "deleteKey: requires 1 argument"
  659     unless @_ == 2;
  660   croak "deleteKey: argument 1 must contain a user_id"
  661     unless defined $userID;
  662 
  663   return $self->{key}->delete($userID);
  664 }
  665 
  666 ################################################################################
  667 # user functions
  668 ################################################################################
  669 
  670 =head2 User Methods
  671 
  672 =over
  673 
  674 =item newUser()
  675 
  676 Returns a new, empty user object.
  677 
  678 =cut
  679 
  680 sub newUser {
  681   my ($self, $prototype) = @_;
  682   return $self->{user}->{record}->new($prototype);
  683 }
  684 
  685 =item listUsers()
  686 
  687 Returns a list of user IDs representing the records in the user table.
  688 
  689 =cut
  690 
  691 sub listUsers {
  692   my ($self) = @_;
  693 
  694   croak "listUsers: requires 0 arguments"
  695     unless @_ == 1;
  696 
  697   return map { $_->[0] }
  698     $self->{user}->list(undef);
  699 }
  700 
  701 =item addUser($User)
  702 
  703 $User is a record object. The user will be added to the user table if a user
  704 with the same user ID does not already exist. If one does exist, an exception
  705 is thrown.
  706 
  707 =cut
  708 
  709 sub addUser {
  710   my ($self, $User) = @_;
  711 
  712   croak "addUser: requires 1 argument"
  713     unless @_ == 2;
  714   croak "addUser: argument 1 must be of type ", $self->{user}->{record}
  715     unless ref $User eq $self->{user}->{record};
  716   croak "addUser: user exists (perhaps you meant to use putUser?)"
  717     if $self->{user}->exists($User->user_id);
  718 
  719   checkKeyfields($User);
  720 
  721   return $self->{user}->add($User);
  722 }
  723 
  724 =item getUser($userID)
  725 
  726 If a record with a matching user ID exists, a record object containting that
  727 record's data will be returned. If no such record exists, an undefined value
  728 will be returned.
  729 
  730 =cut
  731 
  732 sub getUser {
  733   my ($self, $userID) = @_;
  734 
  735   croak "getUser: requires 1 argument"
  736     unless @_ == 2;
  737   croak "getUser: argument 1 must contain a user_id"
  738     unless defined $userID;
  739 
  740   return $self->{user}->get($userID);
  741 }
  742 
  743 =item getUsers(@uesrIDs)
  744 
  745 Return a list of user records associated with the user IDs given. If there is no
  746 record associated with a given user ID, that element of the list will be
  747 undefined.
  748 
  749 =cut
  750 
  751 sub getUsers {
  752   my ($self, @userIDs) = @_;
  753 
  754   croak "getUsers: requires 1 or more argument"
  755     unless @_ >= 2;
  756   foreach my $i (0 .. $#userIDs) {
  757     croak "getUsers: element $i of argument list must contain a user_id"
  758       unless defined $userIDs[$i];
  759   }
  760 
  761   return $self->{user}->gets(@userIDs);
  762 }
  763 
  764 =item putUser($User)
  765 
  766 $User is a record object. If a user record with the same user ID exists in the
  767 user table, the data in the record is replaced with the data in $User. If a
  768 matching user record does not exist, an exception is thrown.
  769 
  770 =cut
  771 
  772 sub putUser {
  773   my ($self, $User) = @_;
  774 
  775   croak "putUser: requires 1 argument"
  776     unless @_ == 2;
  777   croak "putUser: argument 1 must be of type ", $self->{user}->{record}
  778     unless ref $User eq $self->{user}->{record};
  779   croak "putUser: user not found (perhaps you meant to use addUser?)"
  780     unless $self->{user}->exists($User->user_id);
  781 
  782   checkKeyfields($User);
  783 
  784   return $self->{user}->put($User);
  785 }
  786 
  787 =item deleteUser($userID)
  788 
  789 If a user record with a user ID matching $userID exists in the user table, it
  790 is removed and the method returns a true value. If one does exist, a false
  791 value is returned. When a user record is deleted, all records associated with
  792 that user are also deleted. This includes the password, permission, and key
  793 records, and all user set records for that user.
  794 
  795 =cut
  796 
  797 sub deleteUser {
  798   my ($self, $userID) = @_;
  799 
  800   croak "deleteUser: requires 1 argument"
  801     unless @_ == 2;
  802   croak "deleteUser: argument 1 must contain a user_id"
  803     unless defined $userID;
  804 
  805   #$self->deleteUserSet($userID, $_)
  806   # foreach $self->listUserSets($userID);
  807   $self->deleteUserSet($userID, undef);
  808   $self->deletePassword($userID);
  809   $self->deletePermissionLevel($userID);
  810   $self->deleteKey($userID);
  811   return $self->{user}->delete($userID);
  812 }
  813 
  814 =back
  815 
  816 =cut
  817 
  818 ################################################################################
  819 # set functions
  820 ################################################################################
  821 
  822 =head2 Global Set Methods
  823 
  824 FIXME: write this
  825 
  826 =over
  827 
  828 =cut
  829 
  830 sub newGlobalSet {
  831   my ($self, $prototype) = @_;
  832   return $self->{set}->{record}->new($prototype);
  833 }
  834 
  835 sub listGlobalSets($) {
  836   my ($self) = @_;
  837 
  838   croak "listGlobalSets: requires 0 arguments"
  839     unless @_ == 1;
  840 
  841   return map { $_->[0] }
  842     $self->{set}->list(undef);
  843 }
  844 
  845 sub addGlobalSet($$) {
  846   my ($self, $GlobalSet) = @_;
  847 
  848   croak "addGlobalSet: requires 1 argument"
  849     unless @_ == 2;
  850   croak "addGlobalSet: argument 1 must be of type ", $self->{set}->{record}
  851     unless ref $GlobalSet eq $self->{set}->{record};
  852   croak "addGlobalSet: global set exists (perhaps you meant to use putGlobalSet?)"
  853     if $self->{set}->exists($GlobalSet->set_id);
  854 
  855   checkKeyfields($GlobalSet);
  856 
  857   return $self->{set}->add($GlobalSet);
  858 }
  859 
  860 sub getGlobalSet($$) {
  861   my ($self, $setID) = @_;
  862 
  863   croak "getGlobalSet: requires 1 argument"
  864     unless @_ == 2;
  865   croak "getGlobalSet: argument 1 must contain a set_id"
  866     unless defined $setID;
  867 
  868   return $self->{set}->get($setID);
  869 }
  870 
  871 =item getGlobalSets(@setIDs)
  872 
  873 Return a list of global set records associated with the user IDs given. If there
  874 is no record associated with a given user ID, that element of the list will be
  875 undefined.
  876 
  877 =cut
  878 
  879 sub getGlobalSets {
  880   my ($self, @setIDs) = @_;
  881 
  882   croak "getGlobalSets: requires 1 or more argument"
  883     unless @_ >= 2;
  884   foreach my $i (0 .. $#setIDs) {
  885     croak "getGlobalSets: element $i of argument list must contain a set_id"
  886       unless defined $setIDs[$i];
  887   }
  888 
  889   return $self->{set}->gets(@setIDs);
  890 }
  891 
  892 sub putGlobalSet($$) {
  893   my ($self, $GlobalSet) = @_;
  894 
  895   croak "putGlobalSet: requires 1 argument"
  896     unless @_ == 2;
  897   croak "putGlobalSet: argument 1 must be of type ", $self->{set}->{record}
  898     unless ref $GlobalSet eq $self->{set}->{record};
  899   croak "putGlobalSet: global set not found (perhaps you meant to use addGlobalSet?)"
  900     unless $self->{set}->exists($GlobalSet->set_id);
  901 
  902   checkKeyfields($GlobalSet);
  903 
  904   return $self->{set}->put($GlobalSet);
  905 }
  906 
  907 sub deleteGlobalSet($$) {
  908   my ($self, $setID) = @_;
  909 
  910   croak "deleteGlobalSet: requires 1 argument"
  911     unless @_ == 2;
  912   croak "deleteGlobalSet: argument 1 must contain a set_id"
  913     unless defined $setID or caller eq __PACKAGE__;
  914 
  915   #$self->deleteUserSet($_, $setID)
  916   # foreach $self->listSetUsers($setID);
  917   #$self->deleteGlobalProblem($setID, $_)
  918   # foreach $self->listGlobalProblems($setID);
  919   $self->deleteUserSet(undef, $setID);
  920   $self->deleteGlobalProblem($setID, undef);
  921   return $self->{set}->delete($setID);
  922 }
  923 
  924 =back
  925 
  926 =cut
  927 
  928 ################################################################################
  929 # set_user functions
  930 ################################################################################
  931 
  932 =head2 User-Specific Set Methods
  933 
  934 FIXME: write this
  935 
  936 =over
  937 
  938 =cut
  939 
  940 sub newUserSet {
  941   my ($self, $prototype) = @_;
  942   return $self->{set_user}->{record}->new($prototype);
  943 }
  944 
  945 sub listSetUsers($$) {
  946   my ($self, $setID) = @_;
  947 
  948   croak "listSetUsers: requires 1 argument"
  949     unless @_ == 2;
  950   croak "listSetUsers: argument 1 must contain a set_id"
  951     unless defined $setID;
  952 
  953   return map { $_->[0] } # extract user_id
  954     $self->{set_user}->list(undef, $setID);
  955 }
  956 
  957 sub listUserSets($$) {
  958   my ($self, $userID) = @_;
  959 
  960   croak "listUserSets: requires 1 argument"
  961     unless @_ == 2;
  962   croak "listUserSets: argument 1 must contain a user_id"
  963     unless defined $userID;
  964 
  965   return map { $_->[1] } # extract set_id
  966     $self->{set_user}->list($userID, undef);
  967 }
  968 
  969 sub addUserSet($$) {
  970   my ($self, $UserSet) = @_;
  971 
  972   croak "addUserSet: requires 1 argument"
  973     unless @_ == 2;
  974   croak "addUserSet: argument 1 must be of type ", $self->{set_user}->{record}
  975     unless ref $UserSet eq $self->{set_user}->{record};
  976   croak "addUserSet: user set exists (perhaps you meant to use putUserSet?)"
  977     if $self->{set_user}->exists($UserSet->user_id, $UserSet->set_id);
  978   croak "addUserSet: user ", $UserSet->user_id, " not found"
  979     unless $self->{user}->exists($UserSet->user_id);
  980   croak "addUserSet: set ", $UserSet->set_id, " not found"
  981     unless $self->{set}->exists($UserSet->set_id);
  982 
  983   checkKeyfields($UserSet);
  984 
  985   return $self->{set_user}->add($UserSet);
  986 }
  987 
  988 sub getUserSet($$$) {
  989   my ($self, $userID, $setID) = @_;
  990 
  991   croak "getUserSet: requires 2 arguments"
  992     unless @_ == 3;
  993   croak "getUserSet: argument 1 must contain a user_id"
  994     unless defined $userID;
  995   croak "getUserSet: argument 2 must contain a set_id"
  996     unless defined $setID;
  997 
  998   return $self->{set_user}->get($userID, $setID);
  999 }
 1000 
 1001 =item getUserSets(@userSetIDs)
 1002 
 1003 Return a list of user set records associated with the user IDs given. If there
 1004 is no record associated with a given user ID, that element of the list will be
 1005 undefined. @userProblemIDs consists of references to arrays in which the first
 1006 element is the user_id and the second element is the set_id.
 1007 
 1008 =cut
 1009 
 1010 sub getUserSets {
 1011   my ($self, @userSetIDs) = @_;
 1012 
 1013   croak "getUserSets: requires 1 or more argument"
 1014     unless @_ >= 2;
 1015   foreach my $i (0 .. $#userSetIDs) {
 1016     croak "getUserSets: element $i of argument list must contain a <user_id, set_id> pair"
 1017       unless defined $userSetIDs[$i]
 1018              and ref $userSetIDs[$i] eq "ARRAY"
 1019              and @{$userSetIDs[$i]} == 2
 1020              and defined $userSetIDs[$i]->[0]
 1021              and defined $userSetIDs[$i]->[1];
 1022   }
 1023 
 1024   return $self->{set_user}->gets(@userSetIDs);
 1025 }
 1026 
 1027 sub putUserSet($$) {
 1028   my ($self, $UserSet) = @_;
 1029 
 1030   croak "putUserSet: requires 1 argument"
 1031     unless @_ == 2;
 1032   croak "putUserSet: argument 1 must be of type ", $self->{set_user}->{record}
 1033     unless ref $UserSet eq $self->{set_user}->{record};
 1034   croak "putUserSet: user set not found (perhaps you meant to use addUserSet?)"
 1035     unless $self->{set_user}->exists($UserSet->user_id, $UserSet->set_id);
 1036   croak "putUserSet: user ", $UserSet->user_id, " not found"
 1037     unless $self->{user}->exists($UserSet->user_id);
 1038   croak "putUserSet: set ", $UserSet->set_id, " not found"
 1039     unless $self->{set}->exists($UserSet->set_id);
 1040 
 1041   checkKeyfields($UserSet);
 1042 
 1043   return $self->{set_user}->put($UserSet);
 1044 }
 1045 
 1046 sub deleteUserSet($$$) {
 1047   my ($self, $userID, $setID) = @_;
 1048 
 1049   croak "getUserSet: requires 2 arguments"
 1050     unless @_ == 3;
 1051   croak "getUserSet: argument 1 must contain a user_id"
 1052     unless defined $userID or caller eq __PACKAGE__;
 1053   croak "getUserSet: argument 2 must contain a set_id"
 1054     unless defined $userID or caller eq __PACKAGE__;
 1055 
 1056   #$self->deleteUserProblem($userID, $setID, $_)
 1057   # foreach $self->listUserProblems($userID, $setID);
 1058   $self->deleteUserProblem($userID, $setID, undef);
 1059   return $self->{set_user}->delete($userID, $setID);
 1060 }
 1061 
 1062 =back
 1063 
 1064 =cut
 1065 
 1066 ################################################################################
 1067 # problem functions
 1068 ################################################################################
 1069 
 1070 =head2 Global Problem Methods
 1071 
 1072 FIXME: write this
 1073 
 1074 =over
 1075 
 1076 =cut
 1077 
 1078 sub newGlobalProblem {
 1079   my ($self, $prototype) = @_;
 1080   return $self->{problem}->{record}->new($prototype);
 1081 }
 1082 
 1083 sub listGlobalProblems($$) {
 1084   my ($self, $setID) = @_;
 1085 
 1086   croak "listGlobalProblems: requires 1 arguments"
 1087     unless @_ == 2;
 1088   croak "listGlobalProblems: argument 1 must contain a set_id"
 1089     unless defined $setID;
 1090 
 1091   return map { $_->[1] }
 1092     $self->{problem}->list($setID, undef);
 1093 }
 1094 
 1095 sub addGlobalProblem($$) {
 1096   my ($self, $GlobalProblem) = @_;
 1097 
 1098   croak "addGlobalProblem: requires 1 argument"
 1099     unless @_ == 2;
 1100   croak "addGlobalProblem: argument 1 must be of type ", $self->{problem}->{record}
 1101     unless ref $GlobalProblem eq $self->{problem}->{record};
 1102   croak "addGlobalProblem: global problem exists (perhaps you meant to use putGlobalProblem?)"
 1103     if $self->{problem}->exists($GlobalProblem->set_id, $GlobalProblem->problem_id);
 1104   croak "addGlobalProblem: set ", $GlobalProblem->set_id, " not found"
 1105     unless $self->{set}->exists($GlobalProblem->set_id);
 1106 
 1107   checkKeyfields($GlobalProblem);
 1108 
 1109   return $self->{problem}->add($GlobalProblem);
 1110 }
 1111 
 1112 sub getGlobalProblem($$$) {
 1113   my ($self, $setID, $problemID) = @_;
 1114 
 1115   croak "getGlobalProblem: requires 2 arguments"
 1116     unless @_ == 3;
 1117   croak "getGlobalProblem: argument 1 must contain a set_id"
 1118     unless defined $setID;
 1119   croak "getGlobalProblem: argument 2 must contain a problem_id"
 1120     unless defined $problemID;
 1121 
 1122   return $self->{problem}->get($setID, $problemID);
 1123 }
 1124 
 1125 =item getGlobalProblems(@problemIDs)
 1126 
 1127 Return a list of global set records associated with the user IDs given. If there
 1128 is no record associated with a given user ID, that element of the list will be
 1129 undefined. @problemIDs consists of references to arrays in which the first
 1130 element is the set_id, and the second element is the problem_id.
 1131 
 1132 =cut
 1133 
 1134 sub getGlobalProblems {
 1135   my ($self, @problemIDs) = @_;
 1136 
 1137   croak "getGlobalProblems: requires 1 or more argument"
 1138     unless @_ >= 2;
 1139   foreach my $i (0 .. $#problemIDs) {
 1140     croak "getUserSets: element $i of argument list must contain a <set_id, problem_id> pair"
 1141       unless defined $problemIDs[$i]
 1142              and ref $problemIDs[$i] eq "ARRAY"
 1143              and @{$problemIDs[$i]} == 2
 1144              and defined $problemIDs[$i]->[0]
 1145              and defined $problemIDs[$i]->[1];
 1146   }
 1147 
 1148   return $self->{problem}->gets(@problemIDs);
 1149 }
 1150 
 1151 sub putGlobalProblem($$) {
 1152   my ($self, $GlobalProblem) = @_;
 1153 
 1154   croak "putGlobalProblem: requires 1 argument"
 1155     unless @_ == 2;
 1156   croak "putGlobalProblem: argument 1 must be of type ", $self->{problem}->{record}
 1157     unless ref $GlobalProblem eq $self->{problem}->{record};
 1158   croak "putGlobalProblem: global problem not found (perhaps you meant to use addGlobalProblem?)"
 1159     unless $self->{problem}->exists($GlobalProblem->set_id, $GlobalProblem->problem_id);
 1160   croak "putGlobalProblem: set ", $GlobalProblem->set_id, " not found"
 1161     unless $self->{set}->exists($GlobalProblem->set_id);
 1162 
 1163   checkKeyfields($GlobalProblem);
 1164 
 1165   return $self->{problem}->put($GlobalProblem);
 1166 }
 1167 
 1168 sub deleteGlobalProblem($$$) {
 1169   my ($self, $setID, $problemID) = @_;
 1170 
 1171   croak "deleteGlobalProblem: requires 2 arguments"
 1172     unless @_ == 3;
 1173   croak "deleteGlobalProblem: argument 1 must contain a set_id"
 1174     unless defined $setID or caller eq __PACKAGE__;
 1175   croak "deleteGlobalProblem: argument 2 must contain a problem_id"
 1176     unless defined $problemID or caller eq __PACKAGE__;
 1177 
 1178   #$self->deleteUserProblem($_, $setID, $problemID)
 1179   # foreach $self->listProblemUsers($setID, $problemID);
 1180   $self->deleteUserProblem(undef, $setID, $problemID);
 1181   return $self->{problem}->delete($setID, $problemID);
 1182 }
 1183 
 1184 =back
 1185 
 1186 =cut
 1187 
 1188 ################################################################################
 1189 # problem_user functions
 1190 ################################################################################
 1191 
 1192 =head2 User-Specific Problem Methods
 1193 
 1194 FIXME: write this
 1195 
 1196 =over
 1197 
 1198 =cut
 1199 
 1200 sub newUserProblem {
 1201   my ($self, $prototype) = @_;
 1202   return $self->{problem_user}->{record}->new($prototype);
 1203 }
 1204 
 1205 sub listProblemUsers($$$) {
 1206   my ($self, $setID, $problemID) = @_;
 1207 
 1208   croak "listProblemUsers: requires 2 arguments"
 1209     unless @_ == 3;
 1210   croak "listProblemUsers: argument 1 must contain a set_id"
 1211     unless defined $setID;
 1212   croak "listProblemUsers: argument 2 must contain a problem_id"
 1213     unless defined $problemID;
 1214 
 1215   return map { $_->[0] } # extract user_id
 1216     $self->{problem_user}->list(undef, $setID, $problemID);
 1217 }
 1218 
 1219 sub listUserProblems($$$) {
 1220   my ($self, $userID, $setID) = @_;
 1221 
 1222   croak "listUserProblems: requires 2 arguments"
 1223     unless @_ == 3;
 1224   croak "listUserProblems: argument 1 must contain a user_id"
 1225     unless defined $userID;
 1226   croak "listUserProblems: argument 2 must contain a set_id"
 1227     unless defined $setID;
 1228 
 1229   return map { $_->[2] } # extract problem_id
 1230     $self->{problem_user}->list($userID, $setID, undef);
 1231 }
 1232 
 1233 sub addUserProblem($$) {
 1234   my ($self, $UserProblem) = @_;
 1235 
 1236   croak "addUserProblem: requires 1 argument"
 1237     unless @_ == 2;
 1238   croak "addUserProblem: argument 1 must be of type ", $self->{problem_user}->{record}
 1239     unless ref $UserProblem eq $self->{problem_user}->{record};
 1240   croak "addUserProblem: user problem exists (perhaps you meant to use putUserProblem?)"
 1241     if $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
 1242   croak "addUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
 1243     unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
 1244   croak "addUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
 1245     unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
 1246 
 1247   checkKeyfields($UserProblem);
 1248 
 1249   return $self->{problem_user}->add($UserProblem);
 1250 }
 1251 
 1252 sub getUserProblem($$$$) {
 1253   my ($self, $userID, $setID, $problemID) = @_;
 1254 
 1255   croak "getUserProblem: requires 3 arguments"
 1256     unless @_ == 4;
 1257   croak "getUserProblem: argument 1 must contain a user_id"
 1258     unless defined $userID;
 1259   croak "getUserProblem: argument 2 must contain a set_id"
 1260     unless defined $setID;
 1261   croak "getUserProblem: argument 3 must contain a problem_id"
 1262     unless defined $problemID;
 1263 
 1264   return $self->{problem_user}->get($userID, $setID, $problemID);
 1265 }
 1266 
 1267 =item getUserProblems(@userProblemIDs)
 1268 
 1269 Return a list of user set records associated with the user IDs given. If there
 1270 is no record associated with a given user ID, that element of the list will be
 1271 undefined. @userProblemIDs consists of references to arrays in which the first
 1272 element is the user_id, the second element is the set_id, and the third element
 1273 is the problem_id.
 1274 
 1275 =cut
 1276 
 1277 sub getUserProblems {
 1278   my ($self, @userProblemIDs) = @_;
 1279 
 1280   croak "getUserProblems: requires 1 or more argument"
 1281     unless @_ >= 2;
 1282   foreach my $i (0 .. $#userProblemIDs) {
 1283     croak "getUserProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1284       unless defined $userProblemIDs[$i]
 1285              and ref $userProblemIDs[$i] eq "ARRAY"
 1286              and @{$userProblemIDs[$i]} == 3
 1287              and defined $userProblemIDs[$i]->[0]
 1288              and defined $userProblemIDs[$i]->[1]
 1289              and defined $userProblemIDs[$i]->[2];
 1290   }
 1291 
 1292   return $self->{problem_user}->get(@userProblemIDs);
 1293 }
 1294 
 1295 sub putUserProblem($$) {
 1296   my ($self, $UserProblem) = @_;
 1297 
 1298   croak "putUserProblem: requires 1 argument"
 1299     unless @_ == 2;
 1300   croak "putUserProblem: argument 1 must be of type ", $self->{problem_user}->{record}
 1301     unless ref $UserProblem eq $self->{problem_user}->{record};
 1302   croak "putUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
 1303     unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
 1304   croak "putUserProblem: user problem not found (perhaps you meant to use addUserProblem?)"
 1305     unless $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
 1306   croak "putUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
 1307     unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
 1308 
 1309   checkKeyfields($UserProblem);
 1310 
 1311   return $self->{problem_user}->put($UserProblem);
 1312 }
 1313 
 1314 sub deleteUserProblem($$$$) {
 1315   my ($self, $userID, $setID, $problemID) = @_;
 1316 
 1317   croak "getUserProblem: requires 3 arguments"
 1318     unless @_ == 4;
 1319   croak "getUserProblem: argument 1 must contain a user_id"
 1320     unless defined $userID or caller eq __PACKAGE__;
 1321   croak "getUserProblem: argument 2 must contain a set_id"
 1322     unless defined $setID or caller eq __PACKAGE__;
 1323   croak "getUserProblem: argument 3 must contain a problem_id"
 1324     unless defined $problemID or caller eq __PACKAGE__;
 1325 
 1326   return $self->{problem_user}->delete($userID, $setID, $problemID);
 1327 }
 1328 
 1329 =back
 1330 
 1331 =cut
 1332 
 1333 ################################################################################
 1334 # set+set_user functions
 1335 ################################################################################
 1336 
 1337 =head2 Set Merging Methods
 1338 
 1339 FIXME: write this
 1340 
 1341 =over
 1342 
 1343 =cut
 1344 
 1345 sub getGlobalUserSet {
 1346   carp "getGlobalUserSet: this method is deprecated -- use getMergedSet instead";
 1347   return shift->getMergedSet(@_);
 1348 }
 1349 
 1350 sub getMergedSet {
 1351   my ($self, $userID, $setID) = @_;
 1352 
 1353   croak "getMergedSet: requires 2 arguments"
 1354     unless @_ == 3;
 1355   croak "getMergedSet: argument 1 must contain a user_id"
 1356     unless defined $userID;
 1357   croak "getMergedSet: argument 2 must contain a set_id"
 1358     unless defined $setID;
 1359 
 1360   #my $UserSet = $self->getUserSet($userID, $setID);
 1361   #return unless $UserSet;
 1362   #my $GlobalSet = $self->getGlobalSet($setID);
 1363   #if ($GlobalSet) {
 1364   # foreach ($UserSet->FIELDS()) {
 1365   #   next unless $GlobalSet->can($_);
 1366   #   next if $UserSet->$_();
 1367   #   $UserSet->$_($GlobalSet->$_());
 1368   # }
 1369   #}
 1370   #return $UserSet;
 1371 
 1372   return $self->getMergedSets([$userID, $setID]);
 1373 }
 1374 
 1375 =item geMegedSets(@userSetIDs)
 1376 
 1377 Return a list of merged set records associated with the user IDs given. If there
 1378 is no record associated with a given user ID, that element of the list will be
 1379 undefined. @userSetIDs consists of references to arrays in which the first
 1380 element is the user_id and the second element is the set_id.
 1381 
 1382 =cut
 1383 
 1384 sub getMergedSets {
 1385   my ($self, @userSetIDs) = @_;
 1386 
 1387   croak "getMergedSets: requires 1 or more argument"
 1388     unless @_ >= 2;
 1389   foreach my $i (0 .. $#userSetIDs) {
 1390     croak "getMergedSets: element $i of argument list must contain a <user_id, set_id> pair"
 1391       unless defined $userSetIDs[$i]
 1392              and ref $userSetIDs[$i] eq "ARRAY"
 1393              and @{$userSetIDs[$i]} == 2
 1394              and defined $userSetIDs[$i]->[0]
 1395              and defined $userSetIDs[$i]->[1];
 1396   }
 1397 
 1398   my @UserSets = $self->getUserSets(@userSetIDs);
 1399 
 1400   my @globalSetIDs = map { [ $_->[1] ] } @userSetIDs;
 1401   my @GlobalSets = $self->getGlobalSets(@globalSetIDs);
 1402 
 1403   my %globalSetFields = map { $_ => 1 } $self->newGlobalSet->FIELDS;
 1404   my @commonFields = grep { exists $globalSetFields{$_} } $self->newUserSet->FIELDS;
 1405 
 1406   for (my $i = 0; $i < @UserSets; $i++) {
 1407     my $UserSet = $UserSets[$i];
 1408     my $GlobalSet = $GlobalSets[$i];
 1409     next unless $UserSet and $GlobalSet;
 1410     foreach my $field (@commonFields) {
 1411       next if $UserSet->$field;
 1412       $UserSet->$field($GlobalSet->$field);
 1413     }
 1414   }
 1415 
 1416   return @UserSets;
 1417 }
 1418 
 1419 =back
 1420 
 1421 =cut
 1422 
 1423 ################################################################################
 1424 # problem+problem_user functions
 1425 ################################################################################
 1426 
 1427 =head2 Problem Merging Methods
 1428 
 1429 FIXME: write this
 1430 
 1431 =over
 1432 
 1433 =cut
 1434 
 1435 sub getGlobalUserProblem {
 1436   carp "getGlobalUserProblem: this method is deprecated -- use getMergedProblem instead";
 1437   return shift->getMergedProblem(@_);
 1438 }
 1439 
 1440 sub getMergedProblem {
 1441   my ($self, $userID, $setID, $problemID) = @_;
 1442 
 1443   croak "getGlobalUserSet: requires 3 arguments"
 1444     unless @_ == 4;
 1445   croak "getGlobalUserSet: argument 1 must contain a user_id"
 1446     unless defined $userID;
 1447   croak "getGlobalUserSet: argument 2 must contain a set_id"
 1448     unless defined $setID;
 1449   croak "getGlobalUserSet: argument 3 must contain a problem_id"
 1450     unless defined $problemID;
 1451 
 1452   #my $UserProblem = $self->getUserProblem($userID, $setID, $problemID);
 1453   #return unless $UserProblem;
 1454   #my $GlobalProblem = $self->getGlobalProblem($setID, $problemID);
 1455   #if ($GlobalProblem) {
 1456   # foreach ($UserProblem->FIELDS()) {
 1457   #   next unless $GlobalProblem->can($_);
 1458   #   next if $UserProblem->$_();
 1459   #   $UserProblem->$_($GlobalProblem->$_());
 1460   # }
 1461   #}
 1462   #return $UserProblem;
 1463 
 1464   return $self->getMergedProblems([$userID, $setID, $problemID]);
 1465 }
 1466 
 1467 =item getMergedProblems(@userProblemIDs)
 1468 
 1469 Return a list of merged set records associated with the user IDs given. If there
 1470 is no record associated with a given user ID, that element of the list will be
 1471 undefined. @userProblemIDs consists of references to arrays in which the first
 1472 element is the user_id, the second element is the set_id, and the third element
 1473 is the problem_id.
 1474 
 1475 =cut
 1476 
 1477 #sub getMergedProblems {
 1478 # my ($self, @userProblemIDs) = @_;
 1479 #
 1480 # croak "getMergedProblems: requires 1 or more argument"
 1481 #   unless @_ >= 2;
 1482 # foreach my $i (0 .. $#userProblemIDs) {
 1483 #   croak "getMergedProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1484 #     unless defined $userProblemIDs[$i]
 1485 #            and ref $userProblemIDs[$i] eq "ARRAY"
 1486 #            and @{$userProblemIDs[$i]} == 3
 1487 #            and defined $userProblemIDs[$i]->[0]
 1488 #            and defined $userProblemIDs[$i]->[1]
 1489 #            and defined $userProblemIDs[$i]->[2];
 1490 # }
 1491 #
 1492 # return map { $self->getMergedProblem(@{$_}) } @userProblemIDs;
 1493 #}
 1494 
 1495 sub getMergedProblems {
 1496   my ($self, @userProblemIDs) = @_;
 1497 
 1498   croak "getMergedProblems: requires 1 or more argument"
 1499     unless @_ >= 2;
 1500   foreach my $i (0 .. $#userProblemIDs) {
 1501     croak "getMergedProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1502       unless defined $userProblemIDs[$i]
 1503              and ref $userProblemIDs[$i] eq "ARRAY"
 1504              and @{$userProblemIDs[$i]} == 3
 1505              and defined $userProblemIDs[$i]->[0]
 1506              and defined $userProblemIDs[$i]->[1]
 1507              and defined $userProblemIDs[$i]->[2];
 1508   }
 1509 
 1510   my @UserProblems = $self->getUserProblems(@userProblemIDs);
 1511 
 1512   my @globalProblemIDs = map { [ $_->[1], $_->[2] ] } @userProblemIDs;
 1513   my @GlobalProblems = $self->getGlobalProblems(@globalProblemIDs);
 1514 
 1515   my %globalProblemFields = map { $_ => 1 } $self->newGlobalProblem->FIELDS;
 1516   my @commonFields = grep { exists $globalProblemFields{$_} } $self->newUserProblem->FIELDS;
 1517 
 1518   for (my $i = 0; $i < @UserProblems; $i++) {
 1519     my $UserProblem = $UserProblems[$i];
 1520     my $GlobalProblem = $GlobalProblems[$i];
 1521     next unless $UserProblem and $GlobalProblem;
 1522     foreach my $field (@commonFields) {
 1523       next if $UserProblem->$field;
 1524       $UserProblem->$field($GlobalProblem->$field);
 1525     }
 1526   }
 1527 
 1528   return @UserProblems;
 1529 }
 1530 
 1531 =back
 1532 
 1533 =cut
 1534 
 1535 ################################################################################
 1536 # debugging
 1537 ################################################################################
 1538 
 1539 #sub dumpDB($$) {
 1540 # my ($self, $table) = @_;
 1541 # return $self->{$table}->dumpDB();
 1542 #}
 1543 
 1544 ################################################################################
 1545 # sanity checking
 1546 ################################################################################
 1547 
 1548 sub checkKeyfields($) {
 1549   my ($Record) = @_;
 1550   foreach my $keyfield ($Record->KEYFIELDS) {
 1551     my $value = $Record->$keyfield;
 1552     croak "checkKeyfields: $keyfield is empty"
 1553       unless defined $value and $value ne "";
 1554 
 1555     if ($keyfield eq "problem_id") {
 1556       croak "checkKeyfields: invalid characters in $keyfield field: $value (valid characters are [0-9])"
 1557         unless $value =~ m/^\d*$/;
 1558     } else {
 1559       croak "checkKeyfields: invalid characters in $keyfield field: $value (valid characters are [A-Za-z0-9_])"
 1560         unless $value =~ m/^[\w-]*$/;
 1561     }
 1562   }
 1563 }
 1564 
 1565 =head1 AUTHOR
 1566 
 1567 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
 1568 
 1569 =cut
 1570 
 1571 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9