[system] / branches / rel-2-2-dev / webwork2 / lib / WeBWorK / DB.pm Repository:
ViewVC logotype

View of /branches/rel-2-2-dev/webwork2/lib/WeBWorK/DB.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2956 - (download) (as text) (annotate)
Fri Oct 22 23:06:44 2004 UTC (8 years, 6 months ago) by sh002i
Original Path: trunk/webwork2/lib/WeBWorK/DB.pm
File size: 59089 byte(s)
pod fixes

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/DB.pm,v 1.57 2004/10/22 22:59:49 sh002i Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::DB;
   18 
   19 =head1 NAME
   20 
   21 WeBWorK::DB - interface with the WeBWorK databases.
   22 
   23 =head1 SYNOPSIS
   24 
   25  my $db = WeBWorK::DB->new($dbLayout);
   26 
   27  my @userIDs = $db->listUsers();
   28  my $Sam = $db->{user}->{record}->new();
   29 
   30  $Sam->user_id("sammy");
   31  $Sam->first_name("Sam");
   32  $Sam->last_name("Hathaway");
   33  # etc.
   34 
   35  $db->addUser($User);
   36  my $Dennis = $db->getUser("dennis");
   37  $Dennis->status("C");
   38  $db->putUser->($Dennis);
   39 
   40  $db->deleteUser("sammy");
   41 
   42 =head1 DESCRIPTION
   43 
   44 WeBWorK::DB provides a consistent interface to a number of database backends.
   45 Access and modification functions are provided for each logical table used by
   46 the webwork system. The particular backend ("schema" and "driver"), record
   47 class, data source, and additional parameters are specified by the hash
   48 referenced by C<$dbLayout>, usually taken from the course environment.
   49 
   50 =head1 ARCHITECTURE
   51 
   52 The new database system uses a three-tier architecture to insulate each layer
   53 from the adjacent layers.
   54 
   55 =head2 Top Layer: DB
   56 
   57 The top layer of the architecture is the DB module. It provides the methods
   58 listed below, and uses schema modules (via tables) to implement those methods.
   59 
   60          / new* list* exists* add* get* get*s put* delete* \          <- api
   61  +------------------------------------------------------------------+
   62  |                                DB                                |
   63  +------------------------------------------------------------------+
   64   \ password permission key user set set_user problem problem_user /  <- tables
   65 
   66 =head2 Middle Layer: Schemas
   67 
   68 The middle layer of the architecture is provided by one or more schema modules.
   69 They are called "schema" modules because they control the structure of the data
   70 for a table. This includes odd things like the way multiple tables are encoded
   71 in a single hash in the WW1Hash schema, and the encoding scheme used.
   72 
   73 The schema modules provide an API that matches the requirements of the DB
   74 layer, on a per-table basis. Each schema module has a style that determines
   75 which drivers it can interface with. For example, WW1Hash is a "hash" style
   76 schema. SQL is a "dbi" style schema.
   77 
   78 =head3 Examples
   79 
   80 Both WeBWorK 1.x and 2.x courses use:
   81 
   82   / password  permission  key \        / user \      <- tables provided
   83  +-----------------------------+  +----------------+
   84  |          Auth1Hash          |  | Classlist1Hash |
   85  +-----------------------------+  +----------------+
   86              \ hash /                  \ hash /      <- driver style required
   87 
   88 WeBWorK 1.x courses also use:
   89 
   90   / set_user problem_user \       / set problem \
   91  +-------------------------+  +---------------------+
   92  |         WW1Hash         |  | GlobalTableEmulator |
   93  +-------------------------+  +---------------------+
   94            \ hash /                   \ null /
   95 
   96 The GlobalTableEmulator schema emulates the global set and problem tables using
   97 data from the set_user and problem_user tables.
   98 
   99 WeBWorK 2.x courses also use:
  100 
  101   / set set_user problem problem_user \
  102  +-------------------------------------+
  103  |               WW2Hash               |
  104  +-------------------------------------+
  105                  \ hash /
  106 
  107 =head2 Bottom Layer: Drivers
  108 
  109 Driver modules implement a style for a schema. They provide physical access to
  110 a data source containing the data for a table. The style of a driver determines
  111 what methods it provides. All drivers provide C<connect(MODE)> and
  112 C<disconnect()> methods. A hash style driver provides a C<hash()> method which
  113 returns the tied hash. A dbi style driver provides a C<handle()> method which
  114 returns the DBI handle.
  115 
  116 =head3 Examples
  117 
  118   / hash \    / hash \    / hash \  <- style
  119  +--------+  +--------+  +--------+
  120  |   DB   |  |  GDBM  |  |   DB3  |
  121  +--------+  +--------+  +--------+
  122 
  123   / dbi \    / ldap \
  124  +-------+  +--------+
  125  |  SQL  |  |  LDAP  |
  126  +-------+  +--------+
  127 
  128 =head2 Record Types
  129 
  130 In C<%dblayout>, each table is assigned a record class, used for passing
  131 complete records to and from the database. The default record classes are
  132 subclasses of the WeBWorK::DB::Record class, and are named as follows: User,
  133 Password, PermissionLevel, Key, Set, UserSet, Problem, UserProblem. In the
  134 following documentation, a reference the the record class for a table means the
  135 record class currently defined for that table in C<%dbLayout>.
  136 
  137 =cut
  138 
  139 use strict;
  140 use warnings;
  141 use Carp;
  142 use Data::Dumper;
  143 use WeBWorK::Timing;
  144 use WeBWorK::Utils qw(runtime_use);
  145 
  146 ################################################################################
  147 # constructor
  148 ################################################################################
  149 
  150 =head1 CONSTRUCTOR
  151 
  152 =over
  153 
  154 =item new($dbLayout)
  155 
  156 The C<new> method creates a DB object and brings up the underlying schema/driver
  157 structure according to the hash referenced by C<$dbLayout>.
  158 
  159 =back
  160 
  161 =head2 C<$dbLayout> Format
  162 
  163 C<$dbLayout> is a hash reference consisting of items keyed by table names. The
  164 value of each item is a reference to a hash containing the following items:
  165 
  166 =over
  167 
  168 =item record
  169 
  170 The name of a perl module to use for representing the data in a record.
  171 
  172 =item schema
  173 
  174 The name of a perl module to use for access to the table.
  175 
  176 =item driver
  177 
  178 The name of a perl module to use for access to the data source.
  179 
  180 =item source
  181 
  182 The location of the data source that should be used by the driver module.
  183 Depending on the driver, this may be a path, a url, or a DBI spec.
  184 
  185 =item params
  186 
  187 A reference to a hash containing extra information needed by the schema. Some
  188 schemas require parameters, some do not. Consult the documentation for the
  189 schema in question.
  190 
  191 =back
  192 
  193 For each table defined in C<$dbLayout>, C<new> loads the record, schema, and
  194 driver modules. It the schema module's C<tables> method lists the current table
  195 (or contains the string "*") and the output of the schema and driver modules'
  196 C<style> methods match, the table is installed. Otherwise, an exception is
  197 thrown.
  198 
  199 =cut
  200 
  201 sub new($$) {
  202   my ($invocant, $dbLayout) = @_;
  203   my $class = ref($invocant) || $invocant;
  204   my $self = {};
  205   bless $self, $class; # bless this here so we can pass it to the schema
  206 
  207   # load the modules required to handle each table, and create driver
  208   my %dbLayout = %$dbLayout;
  209   foreach my $table (keys %dbLayout) {
  210     my $layout = $dbLayout{$table};
  211     my $record = $layout->{record};
  212     my $schema = $layout->{schema};
  213     my $driver = $layout->{driver};
  214     my $source = $layout->{source};
  215     my $params = $layout->{params};
  216 
  217     runtime_use($record);
  218 
  219     runtime_use($driver);
  220     my $driverObject = eval { $driver->new($source, $params) };
  221     croak "error instantiating DB driver $driver for table $table: $@"
  222       if $@;
  223 
  224     runtime_use($schema);
  225     my $schemaObject = eval { $schema->new(
  226       $self, $driverObject, $table, $record, $params) };
  227     croak "error instantiating DB schema $schema for table $table: $@"
  228       if $@;
  229 
  230     $self->{$table} = $schemaObject;
  231   }
  232 
  233   return $self;
  234 }
  235 
  236 =head1 METHODS
  237 
  238 =cut
  239 
  240 ################################################################################
  241 # general functions
  242 ################################################################################
  243 
  244 =head2 General Methods
  245 
  246 =over
  247 
  248 =cut
  249 
  250 =item hashDatabaseOK($fix)
  251 
  252 If the schema module in use for the C<set> and C<problem> tables is
  253 WeBWorK::DB::Schema::GlobalTableEmulator, the database is checked to make sure
  254 that the "global user" exists and all sets and problems are assigned to it. If
  255 $fix is true, problems found will be fixed: A global user will be created and
  256 all sets/problems assigned to it.
  257 
  258 A list of values is returned. The first value is a boolean value indicating
  259 whether problems remain in the database after hashDatabaseOK() is called. The
  260 remaining values are a list of strings indicating the particular ways in which
  261 the database is (or was) broken.
  262 
  263 =cut
  264 
  265 sub hashDatabaseOK {
  266   my ($self, $fix) = @_;
  267 
  268   my $errorsExist;
  269   my @results;
  270 
  271   ##### do we need to run? #####
  272 
  273   unless (ref $self->{set} eq "WeBWorK::DB::Schema::GlobalTableEmulator") {
  274     #warn "hashDatabaseOK($fix): no checks necessary, set table does not use GlobalTableEmulator.\n";
  275     return 1;
  276   }
  277 
  278   ##### is globalUserID defined? #####
  279 
  280   my $globalUserID = $self->{set}->{params}->{globalUserID};
  281   if ($globalUserID eq "") {
  282     return 0, "globalUserID not specified (fix this in %dbLayout.)";
  283   } else {
  284     #warn "hashDatabaseOK($fix): globalUserID not empty ($globalUserID) -- good.\n";
  285   }
  286 
  287   ##### does a user with ID globalUserID exist? #####
  288 
  289   my $GlobalUser = $self->getUser($globalUserID);
  290   if (defined $GlobalUser) {
  291     #warn "hashDatabaseOK($fix): user with ID '$globalUserID' exists -- good.\n";
  292   } else {
  293     #warn "hashDatabaseOK($fix): user with ID '$globalUserID' not found -- bad!\n";
  294     if ($fix) {
  295       $self->addUser($self->newUser(
  296         user_id => $globalUserID,
  297         first_name => "Global",
  298         last_name => "User",
  299         email_address => "",
  300         student_id => $globalUserID,
  301         status => "D",
  302         section => "",
  303         recitation => "",
  304         comment => "This user is used to store data about global set and problem records when using a hash-style database.",
  305       ));
  306       push @results, "User $globalUserID does not exist -- FIXED.";
  307       #warn "hashDatabaseOK($fix): created user with ID '$globalUserID' -- good.\n";
  308     } else {
  309       # at this point, we don't go on. no global user means everything below is going to fail.
  310       return 0, "User $globalUserID does not exist.";
  311     }
  312   }
  313 
  314   ##### are all sets assigned to the user with ID globalUserID? #####
  315 
  316   # FIXME: this is way too slow!
  317   #my @userSetIDs = $self->{set_user}->list(undef, undef);
  318 
  319   # Timing Data
  320   #
  321   # old method:
  322   # TIMING 36119 1 1087502726.923311 (0.139117) mth143: WeBWorK::DB::hashDatabaseOK: about to get orphaned UserSets
  323   # TIMING 36119 1 1087502768.074221 (41.290027) mth143: WeBWorK::DB::hashDatabaseOK: done getting orphaned UserSets
  324   #
  325   # new method:
  326   # TIMING 36134 0 1087502854.579133 (0.141437) mth143: WeBWorK::DB::hashDatabaseOK: about to get orphaned UserSets
  327   # TIMING 36134 0 1087502856.852504 (2.414808) mth143: WeBWorK::DB::hashDatabaseOK: done getting orphaned UserSets
  328   #
  329   # yay!
  330 
  331   $WeBWorK::timer->continue(__PACKAGE__ . "::hashDatabaseOK: about to get orphaned UserSets") if defined $WeBWorK::timer;
  332 
  333   # ... so instead, we're going to do things manually
  334 
  335   # key: setID, value: hash of userIDs of users to whom this set is assigned
  336   my %orphanUserSets;
  337 
  338   if (ref $self->{set_user} eq "WeBWorK::DB::Schema::WW1Hash") {
  339     # we can only do this with WW1Hash
  340     #warn "the fast way!\n";
  341 
  342     # connect
  343     $self->{set_user}->{driver}->connect("ro")
  344       or return 0, @results, "Failed to connect to set_user database.";
  345 
  346     # get PSVNs for global user (šN)
  347     # this reads from "login<>global_user"
  348     my @globalUserPSVNs = $self->{set_user}->getPSVNsForUser($globalUserID);
  349     #warn "found ", scalar @globalUserPSVNs, " PSVNs for the global user.\n";
  350 
  351     # get setIDs for PSVNs (M)
  352     my @globalUserSetIDs;
  353     foreach my $PSVN (@globalUserPSVNs) {
  354       #warn "getting setID for PSVN '$PSVN'...\n";
  355       my $string = $self->{set_user}->fetchString($PSVN);
  356       my (undef, $setID) = $self->{set_user}->string2IDs($string); # discard userID, problemIDs
  357       push @globalUserSetIDs, $setID;
  358       #warn "got setID '$setID'\n";
  359     }
  360 
  361     # get PSVNs for each setID (šN*M)
  362     # this reads from "set<>$_"
  363     my @okPSVNs = map { $self->{set_user}->getPSVNsForSet($_) } @globalUserSetIDs;
  364     #warn "found ", scalar @okPSVNs, " PSVNs for sets assigned to the global user.\n";
  365 
  366     # get all PSVNs (N*M)
  367     # uses: grep { m/^\d+$/ } keys %{ $self->{driver}->hash() }
  368     my @allPSVNs = $self->{set_user}->getAllPSVNs;
  369     #warn "found ", scalar @allPSVNs, " PSVNs total.\n";
  370 
  371     # eliminate PSVNs of sets that are assigned to the global user
  372     my %allPSVNs;
  373     @allPSVNs{@allPSVNs} = ();
  374 
  375     foreach my $PSVN (@okPSVNs) {
  376       delete $allPSVNs{$PSVN};
  377     }
  378 
  379     #warn "the orphan PSVNs are: ", join(", ", keys %allPSVNs), "\n";
  380 
  381     # get setIDs for orphan PSVNs
  382     foreach my $PSVN (keys %allPSVNs) {
  383       #warn "getting userID and setID for PSVN '$PSVN'...\n";
  384       my $string = $self->{set_user}->fetchString($PSVN);
  385       my ($userID, $setID) = $self->{set_user}->string2IDs($string);
  386       $orphanUserSets{$setID}->{$userID} = 1;
  387       #warn "got setID '$setID' for userID '$userID'\n";
  388     }
  389 
  390     # disconnect
  391     $self->{set_user}->{driver}->disconnect;
  392   } else {
  393     # otherwise, do it the slow way (maybe it's not slow with some other schema?)
  394     #warn "oddly enough, set_user isn't using WW1Hash, so we have to use the slow list() method";
  395     my @userSetIDs = $self->{set_user}->list(undef, undef);
  396 
  397     foreach my $userSetID (@userSetIDs) {
  398       my ($userID, $setID) = @$userSetID;
  399       $orphanUserSets{$setID}->{$userID} = 1;
  400     }
  401 
  402     foreach my $setID (keys %orphanUserSets) {
  403       delete $orphanUserSets{$setID}
  404         if exists $orphanUserSets{$setID}->{$globalUserID};
  405     }
  406   }
  407 
  408   $WeBWorK::timer->continue(__PACKAGE__ . "::hashDatabaseOK: done getting orphaned UserSets") if defined $WeBWorK::timer;
  409 
  410   if (keys %orphanUserSets) {
  411     foreach my $setID (keys %orphanUserSets) {
  412       # detect "false positives" -- sets that are assigned to the global user
  413       # but for some reason don't appear in any set index.
  414       if ($self->{set_user}->exists($globalUserID, $setID)) {
  415         my @userIDs = keys %{$orphanUserSets{$setID}};
  416         warn "Set ID '$setID' for users '@userIDs' do not appear in any set index. Index re-build recommended.\n";
  417         push @results, "Set ID '$setID' for users '@userIDs' do not appear in any set index. Index re-build recommended.\n";
  418       } else {
  419         if ($fix) {
  420           my ($userID) = keys %{$orphanUserSets{$setID}};
  421 
  422           # grab the first UserSet of this set (connect and disconnect required for get1*)
  423           $self->{set_user}->{driver}->connect("ro")
  424             or return 0, @results, "Failed to connect to set_user database.";
  425           my $RawUserSet = $self->{set_user}->get1NoFilter($userID, $setID);
  426           my @RawUserProblems = $self->{problem_user}->getAllNoFilter($userID, $setID);
  427           $self->{set_user}->{driver}->disconnect();
  428           unless ($RawUserSet) {
  429             warn "failed to fetch UserSet '$setID' for user '$userID'!\n";
  430             next;
  431           }
  432 
  433           # change user ID to globalUserID and add to database
  434           $RawUserSet->user_id($globalUserID);
  435           $self->{set_user}->add($RawUserSet);
  436           foreach my $RawUserProblem (@RawUserProblems) {
  437             $RawUserProblem->user_id($globalUserID);
  438             $self->{problem_user}->add($RawUserProblem);
  439             #warn "hashDatabaseOK($fix): assigned problem '", $RawUserProblem->problem_id, "' from set '$setID' to global user '$globalUserID' -- good.\n";
  440           }
  441 
  442           #warn "hashDatabaseOK($fix): assigned set '$setID' to global user '$globalUserID' -- good.\n";
  443           push @results, "Set '$setID' not assigned to global user '$globalUserID' -- FIXED.";
  444         } else {
  445           #warn "hashDatabaseOK($fix): set '$setID' not assigned to global user '$globalUserID' -- bad!\n";
  446           push @results, "Set '$setID' not assigned to global user '$globalUserID'.";
  447         }
  448       }
  449     }
  450   } else {
  451     #warn "hashDatabaseOK($fix): all sets assigned to global user '$globalUserID' -- good.\n";
  452   }
  453 
  454   ##### done! #####
  455 
  456   my $status = not $errorsExist;
  457   return $status, @results;
  458 }
  459 
  460 =back
  461 
  462 =cut
  463 
  464 ################################################################################
  465 # password functions
  466 ################################################################################
  467 
  468 =head2 Password Methods
  469 
  470 =over
  471 
  472 =item newPassword()
  473 
  474 Returns a new, empty password object.
  475 
  476 =cut
  477 
  478 sub newPassword {
  479   my ($self, @prototype) = @_;
  480   return $self->{password}->{record}->new(@prototype);
  481 }
  482 
  483 =item listPasswords()
  484 
  485 Returns a list of user IDs representing the records in the password table.
  486 
  487 =cut
  488 
  489 sub listPasswords {
  490   my ($self) = @_;
  491 
  492   croak "listPasswords: requires 0 arguments"
  493     unless @_ == 1;
  494 
  495   return map { $_->[0] }
  496     $self->{password}->list(undef);
  497 }
  498 
  499 =item addPassword($Password)
  500 
  501 $Password is a record object. The password will be added to the password table
  502 if a password with the same user ID does not already exist. If one does exist,
  503 an exception is thrown. To add a password, a user with a matching user ID must
  504 exist in the user table.
  505 
  506 =cut
  507 
  508 sub addPassword {
  509   my ($self, $Password) = @_;
  510 
  511   croak "addPassword: requires 1 argument"
  512     unless @_ == 2;
  513   croak "addPassword: argument 1 must be of type ", $self->{password}->{record}
  514     unless ref $Password eq $self->{password}->{record};
  515 
  516   checkKeyfields($Password);
  517 
  518   croak "addPassword: password exists (perhaps you meant to use putPassword?)"
  519     if $self->{password}->exists($Password->user_id);
  520   croak "addPassword: user ", $Password->user_id, " not found"
  521     unless $self->{user}->exists($Password->user_id);
  522 
  523   return $self->{password}->add($Password);
  524 }
  525 
  526 =item getPassword($userID)
  527 
  528 If a record with a matching user ID exists, a record object containting that
  529 record's data will be returned. If no such record exists, one will be created.
  530 
  531 =cut
  532 
  533 sub getPassword {
  534   my ($self, $userID) = @_;
  535 
  536   croak "getPassword: requires 1 argument"
  537     unless @_ == 2;
  538   croak "getPassword: argument 1 must contain a user_id"
  539     unless defined $userID;
  540 
  541   #return $self->{password}->get($userID);
  542   return ( $self->getPasswords($userID) )[0];
  543 }
  544 
  545 =item getPasswords(@uesrIDs)
  546 
  547 Return a list of password records associated with the user IDs given. If there
  548 is no record associated with a given user ID, one will be created.
  549 
  550 =cut
  551 
  552 sub getPasswords {
  553   my ($self, @userIDs) = @_;
  554 
  555   #croak "getPasswords: requires 1 or more argument"
  556   # unless @_ >= 2;
  557   foreach my $i (0 .. $#userIDs) {
  558     croak "getPasswords: element $i of argument list must contain a user_id"
  559       unless defined $userIDs[$i];
  560   }
  561 
  562   my @Passwords = $self->{password}->gets(map { [$_] } @userIDs);
  563 
  564   for (my $i = 0; $i < @Passwords; $i++) {
  565     my $Password = $Passwords[$i];
  566     my $userID = $userIDs[$i];
  567     if (not defined $Password) {
  568       if ($self->{user}->exists($userID)) {
  569         $Password = $self->newPassword(user_id => $userID);
  570         eval { $self->addPassword($Password) };
  571         if ($@ and $@ !~ m/password exists/) {
  572           die "error while auto-creating password record for user $userID: \"$@\"";
  573         }
  574       }
  575     }
  576   }
  577 
  578   return @Passwords;
  579 }
  580 
  581 =item putPassword($Password)
  582 
  583 $Password is a record object. If a password record with the same user ID exists
  584 in the password table, the data in the record is replaced with the data in
  585 $Password. If a matching password record does not exist, one will be created.
  586 (This is different from most other "put" methods.)
  587 
  588 =cut
  589 
  590 sub putPassword($$) {
  591   my ($self, $Password) = @_;
  592 
  593   croak "putPassword: requires 1 argument"
  594     unless @_ == 2;
  595   croak "putPassword: argument 1 must be of type ", $self->{password}->{record}
  596     unless ref $Password eq $self->{password}->{record};
  597 
  598   checkKeyfields($Password);
  599 
  600   # For Passwords and PermissionLevels, auto-create a record when it doesn't
  601   # already exist. This should be safe.
  602   if ($self->{password}->exists($Password->user_id)) {
  603     return $self->{password}->put($Password);
  604   } else {
  605     return $self->addPassword($Password);
  606   }
  607 }
  608 
  609 =item deletePassword($userID)
  610 
  611 If a password record with a user ID matching $userID exists in the password
  612 table, it is removed and the method returns a true value. If one does exist,
  613 a false value is returned.
  614 
  615 =cut
  616 
  617 sub deletePassword($$) {
  618   my ($self, $userID) = @_;
  619 
  620   croak "putPassword: requires 1 argument"
  621     unless @_ == 2;
  622   croak "deletePassword: argument 1 must contain a user_id"
  623     unless defined $userID;
  624 
  625   return $self->{password}->delete($userID);
  626 }
  627 
  628 =back
  629 
  630 =cut
  631 
  632 ################################################################################
  633 # permission functions
  634 ################################################################################
  635 
  636 =head2 Permission Level Methods
  637 
  638 =over
  639 
  640 =item newPermissionLevel()
  641 
  642 Returns a new, empty permission level object.
  643 
  644 =cut
  645 
  646 sub newPermissionLevel {
  647   my ($self, @prototype) = @_;
  648   return $self->{permission}->{record}->new(@prototype);
  649 }
  650 
  651 =item listPermissionLevels()
  652 
  653 Returns a list of user IDs representing the records in the permission table.
  654 
  655 =cut
  656 
  657 sub listPermissionLevels($) {
  658   my ($self) = @_;
  659 
  660   croak "listPermissionLevels: requires 0 arguments"
  661     unless @_ == 1;
  662 
  663   return map { $_->[0] }
  664     $self->{permission}->list(undef);
  665 }
  666 
  667 =item addPermissionLevel($PermissionLevel)
  668 
  669 $PermissionLevel is a record object. The permission level will be added to the
  670 permission table if a permission level with the same user ID does not already
  671 exist. If one does exist, an exception is thrown. To add a permission level, a
  672 user with a matching user ID must exist in the user table.
  673 
  674 =cut
  675 
  676 sub addPermissionLevel($$) {
  677   my ($self, $PermissionLevel) = @_;
  678 
  679   croak "addPermissionLevel: requires 1 argument"
  680     unless @_ == 2;
  681   croak "addPermissionLevel: argument 1 must be of type ", $self->{permission}->{record}
  682     unless ref $PermissionLevel eq $self->{permission}->{record};
  683 
  684   checkKeyfields($PermissionLevel);
  685 
  686   croak "addPermissionLevel: permission level exists (perhaps you meant to use putPermissionLevel?)"
  687     if $self->{permission}->exists($PermissionLevel->user_id);
  688   croak "addPermissionLevel: user ", $PermissionLevel->user_id, " not found"
  689     unless $self->{user}->exists($PermissionLevel->user_id);
  690 
  691   return $self->{permission}->add($PermissionLevel);
  692 }
  693 
  694 =item getPermissionLevel($userID)
  695 
  696 If a record with a matching user ID exists, a record object containting that
  697 record's data will be returned. If no such record exists, one will be created.
  698 
  699 =cut
  700 
  701 sub getPermissionLevel($$) {
  702   my ($self, $userID) = @_;
  703 
  704   croak "getPermissionLevel: requires 1 argument"
  705     unless @_ == 2;
  706   croak "getPermissionLevel: argument 1 must contain a user_id"
  707     unless defined $userID;
  708 
  709   #return $self->{permission}->get($userID);
  710   return ( $self->getPermissionLevels($userID) )[0];
  711 }
  712 
  713 =item getPermissionLevels(@uesrIDs)
  714 
  715 Return a list of permission level records associated with the user IDs given. If
  716 there is no record associated with a given user ID, one will be created.
  717 
  718 =cut
  719 
  720 sub getPermissionLevels {
  721   my ($self, @userIDs) = @_;
  722 
  723   #croak "getPermissionLevels: requires 1 or more argument"
  724   # unless @_ >= 2;
  725   foreach my $i (0 .. $#userIDs) {
  726     croak "getPermissionLevels: element $i of argument list must contain a user_id"
  727       unless defined $userIDs[$i];
  728   }
  729 
  730   my @PermissionLevels = $self->{permission}->gets(map { [$_] } @userIDs);
  731 
  732   for (my $i = 0; $i < @PermissionLevels; $i++) {
  733     my $PermissionLevel = $PermissionLevels[$i];
  734     my $userID = $userIDs[$i];
  735     if (not defined $PermissionLevel) {
  736       if ($self->{user}->exists($userID)) {
  737         $PermissionLevel = $self->newPermissionLevel(user_id => $userID);
  738         eval { $self->addPermissionLevel($PermissionLevel) };
  739         if ($@ and $@ !~ m/permission level exists/) {
  740           die "error while auto-creating permission level record for user $userID: \"$@\"";
  741         }
  742         $PermissionLevels[$i] = $PermissionLevel;
  743       }
  744     }
  745   }
  746 
  747   return @PermissionLevels;
  748 }
  749 
  750 =item putPermissionLevel($PermissionLevel)
  751 
  752 $PermissionLevel is a record object. If a permission level record with the same
  753 user ID exists in the permission table, the data in the record is replaced with
  754 the data in $PermissionLevel. If a matching permission level record does not
  755 exist, one will be created. (This is different from most other "put" methods.)
  756 
  757 =cut
  758 
  759 sub putPermissionLevel($$) {
  760   my ($self, $PermissionLevel) = @_;
  761 
  762   croak "putPermissionLevel: requires 1 argument"
  763     unless @_ == 2;
  764   croak "putPermissionLevel: argument 1 must be of type ", $self->{permission}->{record}
  765     unless ref $PermissionLevel eq $self->{permission}->{record};
  766 
  767   checkKeyfields($PermissionLevel);
  768 
  769   # For Passwords and PermissionLevels, auto-create a record when it doesn't
  770   # already exist. This should be safe.
  771   if ($self->{permission}->exists($PermissionLevel->user_id)) {
  772     return $self->{permission}->put($PermissionLevel);
  773   } else {
  774     return $self->{permission}->add($PermissionLevel);
  775   }
  776 }
  777 
  778 =item deletePermissionLevel($userID)
  779 
  780 If a permission level record with a user ID matching $userID exists in the
  781 permission table, it is removed and the method returns a true value. If one
  782 does exist, a false value is returned.
  783 
  784 =cut
  785 
  786 sub deletePermissionLevel($$) {
  787   my ($self, $userID) = @_;
  788 
  789   croak "deletePermissionLevel: requires 1 argument"
  790     unless @_ == 2;
  791   croak "deletePermissionLevel: argument 1 must contain a user_id"
  792     unless defined $userID;
  793 
  794   return $self->{permission}->delete($userID);
  795 }
  796 
  797 =back
  798 
  799 =cut
  800 
  801 ################################################################################
  802 # key functions
  803 ################################################################################
  804 
  805 =head2 Key Methods
  806 
  807 =over
  808 
  809 =item newKey()
  810 
  811 Returns a new, empty key object.
  812 
  813 =cut
  814 
  815 sub newKey {
  816   my ($self, @prototype) = @_;
  817   return $self->{key}->{record}->new(@prototype);
  818 }
  819 
  820 =item listKeys()
  821 
  822 Returns a list of user IDs representing the records in the key table.
  823 
  824 =cut
  825 
  826 sub listKeys($) {
  827   my ($self) = @_;
  828 
  829   croak "listKeys: requires 0 arguments"
  830     unless @_ == 1;
  831 
  832   return map { $_->[0] }
  833     $self->{key}->list(undef);
  834 }
  835 
  836 =item addKey($Key)
  837 
  838 $Key is a record object. The key will be added to the key table if a key with
  839 the same user ID does not already exist. If one does exist, an exception is
  840 thrown. To add a key, a user with a matching user ID must exist in the user
  841 table.
  842 
  843 =cut
  844 
  845 sub addKey($$) {
  846   my ($self, $Key) = @_;
  847 
  848   croak "addKey: requires 1 argument"
  849     unless @_ == 2;
  850   croak "addKey: argument 1 must be of type ", $self->{key}->{record}
  851     unless ref $Key eq $self->{key}->{record};
  852 
  853   checkKeyfields($Key);
  854 
  855   croak "addKey: key exists (perhaps you meant to use putKey?)"
  856     if $self->{key}->exists($Key->user_id);
  857   croak "addKey: user ", $Key->user_id, " not found"
  858     unless $self->{user}->exists($Key->user_id);
  859 
  860   return $self->{key}->add($Key);
  861 }
  862 
  863 =item getKey($userID)
  864 
  865 If a record with a matching user ID exists, a record object containting that
  866 record's data will be returned. If no such record exists, an undefined value
  867 will be returned.
  868 
  869 =cut
  870 
  871 sub getKey($$) {
  872   my ($self, $userID) = @_;
  873 
  874   croak "getKey: requires 1 argument"
  875     unless @_ == 2;
  876   croak "getKey: argument 1 must contain a user_id"
  877     unless defined $userID;
  878 
  879   return $self->{key}->get($userID);
  880 }
  881 
  882 =item getKeys(@uesrIDs)
  883 
  884 Return a list of key records associated with the user IDs given. If there is no
  885 record associated with a given user ID, that element of the list will be
  886 undefined.
  887 
  888 =cut
  889 
  890 sub getKeys {
  891   my ($self, @userIDs) = @_;
  892 
  893   #croak "getKeys: requires 1 or more argument"
  894   # unless @_ >= 2;
  895   foreach my $i (0 .. $#userIDs) {
  896     croak "getKeys: element $i of argument list must contain a user_id"
  897       unless defined $userIDs[$i];
  898   }
  899 
  900   return $self->{key}->gets(map { [$_] } @userIDs);
  901 }
  902 
  903 =item putKey($Key)
  904 
  905 $Key is a record object. If a key record with the same user ID exists in the
  906 key table, the data in the record is replaced with the data in $Key. If a
  907 matching key record does not exist, an exception is thrown.
  908 
  909 =cut
  910 
  911 sub putKey($$) {
  912   my ($self, $Key) = @_;
  913 
  914   croak "putKey: requires 1 argument"
  915     unless @_ == 2;
  916   croak "putKey: argument 1 must be of type ", $self->{key}->{record}
  917     unless ref $Key eq $self->{key}->{record};
  918 
  919   checkKeyfields($Key);
  920 
  921   croak "putKey: key not found (perhaps you meant to use addKey?)"
  922     unless $self->{key}->exists($Key->user_id);
  923 
  924   return $self->{key}->put($Key);
  925 }
  926 
  927 =item deleteKey($userID)
  928 
  929 If a key record with a user ID matching $userID exists in the key table, it is
  930 removed and the method returns a true value. If one does exist, a false value
  931 is returned.
  932 
  933 =cut
  934 
  935 sub deleteKey($$) {
  936   my ($self, $userID) = @_;
  937 
  938   croak "deleteKey: requires 1 argument"
  939     unless @_ == 2;
  940   croak "deleteKey: argument 1 must contain a user_id"
  941     unless defined $userID;
  942 
  943   return $self->{key}->delete($userID);
  944 }
  945 
  946 =back
  947 
  948 =cut
  949 
  950 ################################################################################
  951 # user functions
  952 ################################################################################
  953 
  954 =head2 User Methods
  955 
  956 =over
  957 
  958 =item newUser()
  959 
  960 Returns a new, empty user object.
  961 
  962 =cut
  963 
  964 sub newUser {
  965   my ($self, @prototype) = @_;
  966   return $self->{user}->{record}->new(@prototype);
  967 }
  968 
  969 =item listUsers()
  970 
  971 Returns a list of user IDs representing the records in the user table.
  972 
  973 =cut
  974 
  975 sub listUsers {
  976   my ($self) = @_;
  977 
  978   croak "listUsers: requires 0 arguments"
  979     unless @_ == 1;
  980 
  981   return map { $_->[0] }
  982     $self->{user}->list(undef);
  983 }
  984 
  985 =item addUser($User)
  986 
  987 $User is a record object. The user will be added to the user table if a user
  988 with the same user ID does not already exist. If one does exist, an exception
  989 is thrown.
  990 
  991 =cut
  992 
  993 sub addUser {
  994   my ($self, $User) = @_;
  995 
  996   croak "addUser: requires 1 argument"
  997     unless @_ == 2;
  998   croak "addUser: argument 1 must be of type ", $self->{user}->{record}
  999     unless ref $User eq $self->{user}->{record};
 1000 
 1001   checkKeyfields($User);
 1002 
 1003   croak "addUser: user exists (perhaps you meant to use putUser?)"
 1004     if $self->{user}->exists($User->user_id);
 1005 
 1006   return $self->{user}->add($User);
 1007 }
 1008 
 1009 =item getUser($userID)
 1010 
 1011 If a record with a matching user ID exists, a record object containting that
 1012 record's data will be returned. If no such record exists, an undefined value
 1013 will be returned.
 1014 
 1015 =cut
 1016 
 1017 sub getUser {
 1018   my ($self, $userID) = @_;
 1019 
 1020   croak "getUser: requires 1 argument"
 1021     unless @_ == 2;
 1022   croak "getUser: argument 1 must contain a user_id"
 1023     unless defined $userID;
 1024 
 1025   return $self->{user}->get($userID);
 1026 }
 1027 
 1028 =item getUsers(@uesrIDs)
 1029 
 1030 Return a list of user records associated with the user IDs given. If there is no
 1031 record associated with a given user ID, that element of the list will be
 1032 undefined.
 1033 
 1034 =cut
 1035 
 1036 sub getUsers {
 1037   my ($self, @userIDs) = @_;
 1038 
 1039   #croak "getUsers: requires 1 or more argument"
 1040   # unless @_ >= 2;
 1041   foreach my $i (0 .. $#userIDs) {
 1042     croak "getUsers: element $i of argument list must contain a user_id"
 1043       unless defined $userIDs[$i];
 1044   }
 1045 
 1046   return $self->{user}->gets(map { [$_] } @userIDs);
 1047 }
 1048 
 1049 =item putUser($User)
 1050 
 1051 $User is a record object. If a user record with the same user ID exists in the
 1052 user table, the data in the record is replaced with the data in $User. If a
 1053 matching user record does not exist, an exception is thrown.
 1054 
 1055 =cut
 1056 
 1057 sub putUser {
 1058   my ($self, $User) = @_;
 1059 
 1060   croak "putUser: requires 1 argument"
 1061     unless @_ == 2;
 1062   croak "putUser: argument 1 must be of type ", $self->{user}->{record}
 1063     unless ref $User eq $self->{user}->{record};
 1064 
 1065   checkKeyfields($User);
 1066 
 1067   croak "putUser: user not found (perhaps you meant to use addUser?)"
 1068     unless $self->{user}->exists($User->user_id);
 1069 
 1070   return $self->{user}->put($User);
 1071 }
 1072 
 1073 =item deleteUser($userID)
 1074 
 1075 If a user record with a user ID matching $userID exists in the user table, it
 1076 is removed and the method returns a true value. If one does exist, a false
 1077 value is returned. When a user record is deleted, all records associated with
 1078 that user are also deleted. This includes the password, permission, and key
 1079 records, and all user set records for that user.
 1080 
 1081 =cut
 1082 
 1083 sub deleteUser {
 1084   my ($self, $userID) = @_;
 1085 
 1086   croak "deleteUser: requires 1 argument"
 1087     unless @_ == 2;
 1088   croak "deleteUser: argument 1 must contain a user_id"
 1089     unless defined $userID;
 1090 
 1091   $self->deleteUserSet($userID, undef);
 1092   $self->deletePassword($userID);
 1093   $self->deletePermissionLevel($userID);
 1094   $self->deleteKey($userID);
 1095   return $self->{user}->delete($userID);
 1096 }
 1097 
 1098 =back
 1099 
 1100 =cut
 1101 
 1102 ################################################################################
 1103 # set functions
 1104 ################################################################################
 1105 
 1106 =head2 Global Set Methods
 1107 
 1108 FIXME: write this
 1109 
 1110 =over
 1111 
 1112 =cut
 1113 
 1114 =item newGlobalSet()
 1115 
 1116 =cut
 1117 
 1118 sub newGlobalSet {
 1119   my ($self, @prototype) = @_;
 1120   return $self->{set}->{record}->new(@prototype);
 1121 }
 1122 
 1123 =item listGlobalSets()
 1124 
 1125 =cut
 1126 
 1127 sub listGlobalSets {
 1128   my ($self) = @_;
 1129 
 1130   croak "listGlobalSets: requires 0 arguments"
 1131     unless @_ == 1;
 1132 
 1133   return map { $_->[0] }
 1134     $self->{set}->list(undef);
 1135 }
 1136 
 1137 =item addGlobalSet($GlobalSet)
 1138 
 1139 =cut
 1140 
 1141 sub addGlobalSet {
 1142   my ($self, $GlobalSet) = @_;
 1143 
 1144   croak "addGlobalSet: requires 1 argument"
 1145     unless @_ == 2;
 1146   croak "addGlobalSet: argument 1 must be of type ", $self->{set}->{record}
 1147     unless ref $GlobalSet eq $self->{set}->{record};
 1148 
 1149   checkKeyfields($GlobalSet);
 1150 
 1151   croak "addGlobalSet: global set exists (perhaps you meant to use putGlobalSet?)"
 1152     if $self->{set}->exists($GlobalSet->set_id);
 1153 
 1154   return $self->{set}->add($GlobalSet);
 1155 }
 1156 
 1157 =item addGlobalSet($setID)
 1158 
 1159 =cut
 1160 
 1161 sub getGlobalSet {
 1162   my ($self, $setID) = @_;
 1163 
 1164   croak "getGlobalSet: requires 1 argument"
 1165     unless @_ == 2;
 1166   croak "getGlobalSet: argument 1 must contain a set_id"
 1167     unless defined $setID;
 1168 
 1169   return $self->{set}->get($setID);
 1170 }
 1171 
 1172 =item getGlobalSets(@setIDs)
 1173 
 1174 Return a list of global set records associated with the record IDs given. If
 1175 there is no record associated with a given record ID, that element of the list
 1176 will be undefined.
 1177 
 1178 =cut
 1179 
 1180 sub getGlobalSets {
 1181   my ($self, @setIDs) = @_;
 1182 
 1183   #croak "getGlobalSets: requires 1 or more argument"
 1184   # unless @_ >= 2;
 1185   foreach my $i (0 .. $#setIDs) {
 1186     croak "getGlobalSets: element $i of argument list must contain a set_id"
 1187       unless defined $setIDs[$i];
 1188   }
 1189 
 1190   return $self->{set}->gets(map { [$_] } @setIDs);
 1191 }
 1192 
 1193 =item addGlobalSet($GlobalSet)
 1194 
 1195 =cut
 1196 
 1197 sub putGlobalSet {
 1198   my ($self, $GlobalSet) = @_;
 1199 
 1200   croak "putGlobalSet: requires 1 argument"
 1201     unless @_ == 2;
 1202   croak "putGlobalSet: argument 1 must be of type ", $self->{set}->{record}
 1203     unless ref $GlobalSet eq $self->{set}->{record};
 1204 
 1205   checkKeyfields($GlobalSet);
 1206 
 1207   croak "putGlobalSet: global set not found (perhaps you meant to use addGlobalSet?)"
 1208     unless $self->{set}->exists($GlobalSet->set_id);
 1209 
 1210   return $self->{set}->put($GlobalSet);
 1211 }
 1212 
 1213 =item addGlobalSet($setID)
 1214 
 1215 =cut
 1216 
 1217 sub deleteGlobalSet {
 1218   my ($self, $setID) = @_;
 1219 
 1220   croak "deleteGlobalSet: requires 1 argument"
 1221     unless @_ == 2;
 1222   croak "deleteGlobalSet: argument 1 must contain a set_id"
 1223     unless defined $setID or caller eq __PACKAGE__;
 1224 
 1225   $self->deleteUserSet(undef, $setID);
 1226   $self->deleteGlobalProblem($setID, undef);
 1227   return $self->{set}->delete($setID);
 1228 }
 1229 
 1230 =back
 1231 
 1232 =cut
 1233 
 1234 ################################################################################
 1235 # set_user functions
 1236 ################################################################################
 1237 
 1238 =head2 User-Specific Set Methods
 1239 
 1240 FIXME: write this
 1241 
 1242 =over
 1243 
 1244 =cut
 1245 
 1246 sub newUserSet {
 1247   my ($self, @prototype) = @_;
 1248   return $self->{set_user}->{record}->new(@prototype);
 1249 }
 1250 
 1251 sub countSetUsers {
 1252   my ($self, $setID) = @_;
 1253 
 1254   croak "countSetUsers: requires 1 argument"
 1255     unless @_ == 2;
 1256   croak "countSetUsers: argument 1 must contain a set_id"
 1257     unless defined $setID;
 1258 
 1259   # inefficient way
 1260   #return scalar $self->{set_user}->list(undef, $setID);
 1261 
 1262   # efficient way
 1263   return $self->{set_user}->count(undef, $setID);
 1264 }
 1265 
 1266 sub listSetUsers {
 1267   my ($self, $setID) = @_;
 1268 
 1269   carp "listSetUsers called in SCALAR context: use countSetUsers instead!\n"
 1270     unless wantarray;
 1271 
 1272   croak "listSetUsers: requires 1 argument"
 1273     unless @_ == 2;
 1274   croak "listSetUsers: argument 1 must contain a set_id"
 1275     unless defined $setID;
 1276 
 1277   return map { $_->[0] } # extract user_id
 1278     $self->{set_user}->list(undef, $setID);
 1279 }
 1280 
 1281 sub countUserSets {
 1282   my ($self, $userID) = @_;
 1283 
 1284   croak "countUserSets: requires 1 argument"
 1285     unless @_ == 2;
 1286   croak "countUserSets: argument 1 must contain a user_id"
 1287     unless defined $userID;
 1288 
 1289   return $self->{set_user}->count($userID, undef);
 1290 }
 1291 
 1292 sub listUserSets {
 1293   my ($self, $userID) = @_;
 1294 
 1295   croak "listUserSets: requires 1 argument"
 1296     unless @_ == 2;
 1297   croak "listUserSets: argument 1 must contain a user_id"
 1298     unless defined $userID;
 1299 
 1300   return map { $_->[1] } # extract set_id
 1301     $self->{set_user}->list($userID, undef);
 1302 }
 1303 
 1304 sub addUserSet {
 1305   my ($self, $UserSet) = @_;
 1306 
 1307   croak "addUserSet: requires 1 argument"
 1308     unless @_ == 2;
 1309   croak "addUserSet: argument 1 must be of type ", $self->{set_user}->{record}
 1310     unless ref $UserSet eq $self->{set_user}->{record};
 1311 
 1312   checkKeyfields($UserSet);
 1313 
 1314   croak "addUserSet: user set exists (perhaps you meant to use putUserSet?)"
 1315     if $self->{set_user}->exists($UserSet->user_id, $UserSet->set_id);
 1316   croak "addUserSet: user ", $UserSet->user_id, " not found"
 1317     unless $self->{user}->exists($UserSet->user_id);
 1318   croak "addUserSet: set ", $UserSet->set_id, " not found"
 1319     unless $self->{set}->exists($UserSet->set_id);
 1320 
 1321   return $self->{set_user}->add($UserSet);
 1322 }
 1323 
 1324 sub getUserSet {
 1325   my ($self, $userID, $setID) = @_;
 1326 
 1327   croak "getUserSet: requires 2 arguments"
 1328     unless @_ == 3;
 1329   croak "getUserSet: argument 1 must contain a user_id"
 1330     unless defined $userID;
 1331   croak "getUserSet: argument 2 must contain a set_id"
 1332     unless defined $setID;
 1333 
 1334   #return $self->{set_user}->get($userID, $setID);
 1335   return ( $self->getUserSets([$userID, $setID]) )[0];
 1336 }
 1337 
 1338 =item getUserSets(@userSetIDs)
 1339 
 1340 Return a list of user set records associated with the record IDs given. If there
 1341 is no record associated with a given record ID, that element of the list will be
 1342 undefined. @userProblemIDs consists of references to arrays in which the first
 1343 element is the user_id and the second element is the set_id.
 1344 
 1345 =cut
 1346 
 1347 sub getUserSets {
 1348   my ($self, @userSetIDs) = @_;
 1349 
 1350   #croak "getUserSets: requires 1 or more argument"
 1351   # unless @_ >= 2;
 1352   foreach my $i (0 .. $#userSetIDs) {
 1353     croak "getUserSets: element $i of argument list must contain a <user_id, set_id> pair"
 1354       unless defined $userSetIDs[$i]
 1355              and ref $userSetIDs[$i] eq "ARRAY"
 1356              and @{$userSetIDs[$i]} == 2
 1357              and defined $userSetIDs[$i]->[0]
 1358              and defined $userSetIDs[$i]->[1];
 1359   }
 1360 
 1361   return $self->{set_user}->gets(@userSetIDs);
 1362 }
 1363 
 1364 sub putUserSet {
 1365   my ($self, $UserSet) = @_;
 1366 
 1367   croak "putUserSet: requires 1 argument"
 1368     unless @_ == 2;
 1369   croak "putUserSet: argument 1 must be of type ", $self->{set_user}->{record}
 1370     unless ref $UserSet eq $self->{set_user}->{record};
 1371 
 1372   checkKeyfields($UserSet);
 1373 
 1374   croak "putUserSet: user set not found (perhaps you meant to use addUserSet?)"
 1375     unless $self->{set_user}->exists($UserSet->user_id, $UserSet->set_id);
 1376   croak "putUserSet: user ", $UserSet->user_id, " not found"
 1377     unless $self->{user}->exists($UserSet->user_id);
 1378   croak "putUserSet: set ", $UserSet->set_id, " not found"
 1379     unless $self->{set}->exists($UserSet->set_id);
 1380 
 1381   return $self->{set_user}->put($UserSet);
 1382 }
 1383 
 1384 sub deleteUserSet {
 1385   my ($self, $userID, $setID) = @_;
 1386 
 1387   croak "getUserSet: requires 2 arguments"
 1388     unless @_ == 3;
 1389   croak "getUserSet: argument 1 must contain a user_id"
 1390     unless defined $userID or caller eq __PACKAGE__;
 1391   croak "getUserSet: argument 2 must contain a set_id"
 1392     unless defined $userID or caller eq __PACKAGE__;
 1393 
 1394   $self->deleteUserProblem($userID, $setID, undef);
 1395   return $self->{set_user}->delete($userID, $setID);
 1396 }
 1397 
 1398 =back
 1399 
 1400 =cut
 1401 
 1402 ################################################################################
 1403 # problem functions
 1404 ################################################################################
 1405 
 1406 =head2 Global Problem Methods
 1407 
 1408 FIXME: write this
 1409 
 1410 =over
 1411 
 1412 =cut
 1413 
 1414 sub newGlobalProblem {
 1415   my ($self, @prototype) = @_;
 1416   return $self->{problem}->{record}->new(@prototype);
 1417 }
 1418 
 1419 sub listGlobalProblems {
 1420   my ($self, $setID) = @_;
 1421 
 1422   croak "listGlobalProblems: requires 1 arguments"
 1423     unless @_ == 2;
 1424   croak "listGlobalProblems: argument 1 must contain a set_id"
 1425     unless defined $setID;
 1426 
 1427   return map { $_->[1] }
 1428     $self->{problem}->list($setID, undef);
 1429 }
 1430 
 1431 sub addGlobalProblem {
 1432   my ($self, $GlobalProblem) = @_;
 1433 
 1434   croak "addGlobalProblem: requires 1 argument"
 1435     unless @_ == 2;
 1436   croak "addGlobalProblem: argument 1 must be of type ", $self->{problem}->{record}
 1437     unless ref $GlobalProblem eq $self->{problem}->{record};
 1438 
 1439   checkKeyfields($GlobalProblem);
 1440 
 1441   croak "addGlobalProblem: global problem exists (perhaps you meant to use putGlobalProblem?)"
 1442     if $self->{problem}->exists($GlobalProblem->set_id, $GlobalProblem->problem_id);
 1443   croak "addGlobalProblem: set ", $GlobalProblem->set_id, " not found"
 1444     unless $self->{set}->exists($GlobalProblem->set_id);
 1445 
 1446   return $self->{problem}->add($GlobalProblem);
 1447 }
 1448 
 1449 sub getGlobalProblem {
 1450   my ($self, $setID, $problemID) = @_;
 1451 
 1452   croak "getGlobalProblem: requires 2 arguments"
 1453     unless @_ == 3;
 1454   croak "getGlobalProblem: argument 1 must contain a set_id"
 1455     unless defined $setID;
 1456   croak "getGlobalProblem: argument 2 must contain a problem_id"
 1457     unless defined $problemID;
 1458 
 1459   return $self->{problem}->get($setID, $problemID);
 1460 }
 1461 
 1462 =item getGlobalProblems(@problemIDs)
 1463 
 1464 Return a list of global set records associated with the record IDs given. If
 1465 there is no record associated with a given record ID, that element of the list
 1466 will be undefined. @problemIDs consists of references to arrays in which the
 1467 first element is the set_id, and the second element is the problem_id.
 1468 
 1469 =cut
 1470 
 1471 sub getGlobalProblems {
 1472   my ($self, @problemIDs) = @_;
 1473 
 1474   #croak "getGlobalProblems: requires 1 or more argument"
 1475   # unless @_ >= 2;
 1476   foreach my $i (0 .. $#problemIDs) {
 1477     croak "getUserSets: element $i of argument list must contain a <set_id, problem_id> pair"
 1478       unless defined $problemIDs[$i]
 1479              and ref $problemIDs[$i] eq "ARRAY"
 1480              and @{$problemIDs[$i]} == 2
 1481              and defined $problemIDs[$i]->[0]
 1482              and defined $problemIDs[$i]->[1];
 1483   }
 1484 
 1485   return $self->{problem}->gets(@problemIDs);
 1486 }
 1487 
 1488 =item getAllGlobalProblems($setID)
 1489 
 1490 Returns a list of Problem objects representing all the problems in the given
 1491 global set. When using the WW1Hash/GlobalTableEmulator schemas, this is far
 1492 more efficient than using listGlobalProblems and getGlobalProblems.
 1493 
 1494 =cut
 1495 
 1496 sub getAllGlobalProblems {
 1497   my ($self, $setID) = @_;
 1498 
 1499   croak "getAllGlobalProblems: requires 1 arguments"
 1500     unless @_ == 2;
 1501   croak "getAllGlobalProblems: argument 1 must contain a set_id"
 1502     unless defined $setID;
 1503 
 1504   if ($self->{problem}->can("getAll")) {
 1505     return $self->{problem}->getAll($setID);
 1506   } else {
 1507     my @problemIDPairs = $self->{problem}->list($setID, undef);
 1508     return $self->{problem}->gets(@problemIDPairs);
 1509   }
 1510 }
 1511 
 1512 sub putGlobalProblem {
 1513   my ($self, $GlobalProblem) = @_;
 1514 
 1515   croak "putGlobalProblem: requires 1 argument"
 1516     unless @_ == 2;
 1517   croak "putGlobalProblem: argument 1 must be of type ", $self->{problem}->{record}
 1518     unless ref $GlobalProblem eq $self->{problem}->{record};
 1519 
 1520   checkKeyfields($GlobalProblem);
 1521 
 1522   croak "putGlobalProblem: global problem not found (perhaps you meant to use addGlobalProblem?)"
 1523     unless $self->{problem}->exists($GlobalProblem->set_id, $GlobalProblem->problem_id);
 1524   croak "putGlobalProblem: set ", $GlobalProblem->set_id, " not found"
 1525     unless $self->{set}->exists($GlobalProblem->set_id);
 1526 
 1527   return $self->{problem}->put($GlobalProblem);
 1528 }
 1529 
 1530 sub deleteGlobalProblem {
 1531   my ($self, $setID, $problemID) = @_;
 1532 
 1533   croak "deleteGlobalProblem: requires 2 arguments"
 1534     unless @_ == 3;
 1535   croak "deleteGlobalProblem: argument 1 must contain a set_id"
 1536     unless defined $setID or caller eq __PACKAGE__;
 1537   croak "deleteGlobalProblem: argument 2 must contain a problem_id"
 1538     unless defined $problemID or caller eq __PACKAGE__;
 1539 
 1540   $self->deleteUserProblem(undef, $setID, $problemID);
 1541   return $self->{problem}->delete($setID, $problemID);
 1542 }
 1543 
 1544 =back
 1545 
 1546 =cut
 1547 
 1548 ################################################################################
 1549 # problem_user functions
 1550 ################################################################################
 1551 
 1552 =head2 User-Specific Problem Methods
 1553 
 1554 FIXME: write this
 1555 
 1556 =over
 1557 
 1558 =cut
 1559 
 1560 sub newUserProblem {
 1561   my ($self, @prototype) = @_;
 1562   return $self->{problem_user}->{record}->new(@prototype);
 1563 }
 1564 
 1565 sub countProblemUsers {
 1566   my ($self, $setID, $problemID) = @_;
 1567 
 1568   croak "countProblemUsers: requires 2 arguments"
 1569     unless @_ == 3;
 1570   croak "countProblemUsers: argument 1 must contain a set_id"
 1571     unless defined $setID;
 1572   croak "countProblemUsers: argument 2 must contain a problem_id"
 1573     unless defined $problemID;
 1574 
 1575   # the slow way
 1576   #return scalar $self->{problem_user}->list(undef, $setID, $problemID);
 1577 
 1578   # the fast way
 1579   return $self->{problem_user}->count(undef, $setID, $problemID);
 1580 }
 1581 
 1582 sub listProblemUsers {
 1583   my ($self, $setID, $problemID) = @_;
 1584 
 1585   carp "listProblemUsers called in SCALAR context: use countProblemUsers instead!\n"
 1586     unless wantarray;
 1587 
 1588   croak "listProblemUsers: requires 2 arguments"
 1589     unless @_ == 3;
 1590   croak "listProblemUsers: argument 1 must contain a set_id"
 1591     unless defined $setID;
 1592   croak "listProblemUsers: argument 2 must contain a problem_id"
 1593     unless defined $problemID;
 1594 
 1595   return map { $_->[0] } # extract user_id
 1596     $self->{problem_user}->list(undef, $setID, $problemID);
 1597 }
 1598 
 1599 sub listUserProblems {
 1600   my ($self, $userID, $setID) = @_;
 1601 
 1602   croak "listUserProblems: requires 2 arguments"
 1603     unless @_ == 3;
 1604   croak "listUserProblems: argument 1 must contain a user_id"
 1605     unless defined $userID;
 1606   croak "listUserProblems: argument 2 must contain a set_id"
 1607     unless defined $setID;
 1608 
 1609   return map { $_->[2] } # extract problem_id
 1610     $self->{problem_user}->list($userID, $setID, undef);
 1611 }
 1612 
 1613 sub addUserProblem {
 1614   my ($self, $UserProblem) = @_;
 1615 
 1616   croak "addUserProblem: requires 1 argument"
 1617     unless @_ == 2;
 1618   croak "addUserProblem: argument 1 must be of type ", $self->{problem_user}->{record}
 1619     unless ref $UserProblem eq $self->{problem_user}->{record};
 1620 
 1621   checkKeyfields($UserProblem);
 1622 
 1623   croak "addUserProblem: user problem exists (perhaps you meant to use putUserProblem?)"
 1624     if $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
 1625   croak "addUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
 1626     unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
 1627   croak "addUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
 1628     unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
 1629 
 1630   return $self->{problem_user}->add($UserProblem);
 1631 }
 1632 
 1633 sub getUserProblem {
 1634   my ($self, $userID, $setID, $problemID) = @_;
 1635 
 1636   croak "getUserProblem: requires 3 arguments"
 1637     unless @_ == 4;
 1638   croak "getUserProblem: argument 1 must contain a user_id"
 1639     unless defined $userID;
 1640   croak "getUserProblem: argument 2 must contain a set_id"
 1641     unless defined $setID;
 1642   croak "getUserProblem: argument 3 must contain a problem_id"
 1643     unless defined $problemID;
 1644 
 1645   return ( $self->getUserProblems([$userID, $setID, $problemID]) )[0];
 1646 }
 1647 
 1648 =item getUserProblems(@userProblemIDs)
 1649 
 1650 Return a list of user set records associated with the user IDs given. If there
 1651 is no record associated with a given user ID, that element of the list will be
 1652 undefined. @userProblemIDs consists of references to arrays in which the first
 1653 element is the user_id, the second element is the set_id, and the third element
 1654 is the problem_id.
 1655 
 1656 =cut
 1657 
 1658 sub getUserProblems {
 1659   my ($self, @userProblemIDs) = @_;
 1660 
 1661   #croak "getUserProblems: requires 1 or more argument"
 1662   # unless @_ >= 2;
 1663   foreach my $i (0 .. $#userProblemIDs) {
 1664     croak "getUserProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1665       unless defined $userProblemIDs[$i]
 1666              and ref $userProblemIDs[$i] eq "ARRAY"
 1667              and @{$userProblemIDs[$i]} == 3
 1668              and defined $userProblemIDs[$i]->[0]
 1669              and defined $userProblemIDs[$i]->[1]
 1670              and defined $userProblemIDs[$i]->[2];
 1671   }
 1672 
 1673   return $self->{problem_user}->gets(@userProblemIDs);
 1674 }
 1675 
 1676 =item getAllUserProblems($userID, $setID)
 1677 
 1678 Returns a list of UserProblem objects representing all the problems in the
 1679 given set. When using the WW1Hash/GlobalTableEmulator schemas, this is far
 1680 more efficient than using listUserProblems and getUserProblems.
 1681 
 1682 =cut
 1683 
 1684 sub getAllUserProblems {
 1685   my ($self, $userID, $setID) = @_;
 1686 
 1687   croak "getAllUserProblems: requires 2 arguments"
 1688     unless @_ == 3;
 1689   croak "getAllUserProblems: argument 1 must contain a user_id"
 1690     unless defined $userID;
 1691   croak "getAllUserProblems: argument 2 must contain a set_id"
 1692     unless defined $setID;
 1693 
 1694   if ($self->{problem_user}->can("getAll")) {
 1695     return $self->{problem_user}->getAll($userID, $setID);
 1696   } else {
 1697     my @problemIDTriples = $self->{problem_user}->list($userID, $setID, undef);
 1698     return $self->{problem_user}->gets(@problemIDTriples);
 1699   }
 1700 }
 1701 
 1702 sub putUserProblem {
 1703   my ($self, $UserProblem) = @_;
 1704 
 1705   croak "putUserProblem: requires 1 argument"
 1706     unless @_ == 2;
 1707   croak "putUserProblem: argument 1 must be of type ", $self->{problem_user}->{record}
 1708     unless ref $UserProblem eq $self->{problem_user}->{record};
 1709 
 1710   checkKeyfields($UserProblem);
 1711 
 1712   croak "putUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
 1713     unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
 1714   croak "putUserProblem: user problem not found (perhaps you meant to use addUserProblem?)"
 1715     unless $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
 1716   croak "putUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
 1717     unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
 1718 
 1719   return $self->{problem_user}->put($UserProblem);
 1720 }
 1721 
 1722 sub deleteUserProblem {
 1723   my ($self, $userID, $setID, $problemID) = @_;
 1724 
 1725   croak "getUserProblem: requires 3 arguments"
 1726     unless @_ == 4;
 1727   croak "getUserProblem: argument 1 must contain a user_id"
 1728     unless defined $userID or caller eq __PACKAGE__;
 1729   croak "getUserProblem: argument 2 must contain a set_id"
 1730     unless defined $setID or caller eq __PACKAGE__;
 1731   croak "getUserProblem: argument 3 must contain a problem_id"
 1732     unless defined $problemID or caller eq __PACKAGE__;
 1733 
 1734   return $self->{problem_user}->delete($userID, $setID, $problemID);
 1735 }
 1736 
 1737 =back
 1738 
 1739 =cut
 1740 
 1741 ################################################################################
 1742 # set+set_user functions
 1743 ################################################################################
 1744 
 1745 =head2 Set Merging Methods
 1746 
 1747 These functions combine a global set and a user set to create a merged set,
 1748 which is returned. Any field that is not defined in the user set is taken from
 1749 the global set. Merged sets have the same type as user sets.
 1750 
 1751 =over
 1752 
 1753 =cut
 1754 
 1755 sub getGlobalUserSet {
 1756   carp "getGlobalUserSet: this method is deprecated -- use getMergedSet instead";
 1757   return shift->getMergedSet(@_);
 1758 }
 1759 
 1760 =item getMergedSet($userID, $setID)
 1761 
 1762 Returns a merged set record associated with the record IDs given. If there is no
 1763 record associated with a given record ID, the undefined value is returned.
 1764 
 1765 =cut
 1766 
 1767 sub getMergedSet {
 1768   my ($self, $userID, $setID) = @_;
 1769 
 1770   croak "getMergedSet: requires 2 arguments"
 1771     unless @_ == 3;
 1772   croak "getMergedSet: argument 1 must contain a user_id"
 1773     unless defined $userID;
 1774   croak "getMergedSet: argument 2 must contain a set_id"
 1775     unless defined $setID;
 1776 
 1777   return ( $self->getMergedSets([$userID, $setID]) )[0];
 1778 }
 1779 
 1780 =item getMegedSets(@userSetIDs)
 1781 
 1782 Return a list of merged set records associated with the record IDs given. If
 1783 there is no record associated with a given record ID, that element of the list
 1784 will be undefined. @userSetIDs consists of references to arrays in which the
 1785 first element is the user_id and the second element is the set_id.
 1786 
 1787 =cut
 1788 
 1789 sub getMergedSets {
 1790   my ($self, @userSetIDs) = @_;
 1791 
 1792   #croak "getMergedSets: requires 1 or more argument"
 1793   # unless @_ >= 2;
 1794   foreach my $i (0 .. $#userSetIDs) {
 1795     croak "getMergedSets: element $i of argument list must contain a <user_id, set_id> pair"
 1796       unless defined $userSetIDs[$i]
 1797              and ref $userSetIDs[$i] eq "ARRAY"
 1798              and @{$userSetIDs[$i]} == 2
 1799              and defined $userSetIDs[$i]->[0]
 1800              and defined $userSetIDs[$i]->[1];
 1801   }
 1802 
 1803   # a horrible, terrible hack ;)
 1804   if (ref $self->{set_user} eq "WeBWorK::DB::Schema::WW1Hash"
 1805       and ref $self->{set} eq "WeBWorK::DB::Schema::GlobalTableEmulator") {
 1806     #warn __PACKAGE__.": using a terrible hack.\n";
 1807     $WeBWorK::timer->continue("DB: getsNoFilter start") if defined($WeBWorK::timer);
 1808     my @MergedSets = $self->{set_user}->getsNoFilter(@userSetIDs);
 1809     $WeBWorK::timer->continue("DB: getsNoFilter end") if defined($WeBWorK::timer);
 1810     return @MergedSets;
 1811   }
 1812 
 1813   $WeBWorK::timer->continue("DB: getUserSets start") if defined($WeBWorK::timer);
 1814   my @UserSets = $self->getUserSets(@userSetIDs); # checked
 1815 
 1816   $WeBWorK::timer->continue("DB: pull out set IDs start") if defined($WeBWorK::timer);
 1817   my @globalSetIDs = map { $_->[1] } @userSetIDs;
 1818   $WeBWorK::timer->continue("DB: getGlobalSets start") if defined($WeBWorK::timer);
 1819   my @GlobalSets = $self->getGlobalSets(@globalSetIDs); # checked
 1820 
 1821   $WeBWorK::timer->continue("DB: calc common fields start") if defined($WeBWorK::timer);
 1822   my %globalSetFields = map { $_ => 1 } $self->newGlobalSet->FIELDS;
 1823   my @commonFields = grep { exists $globalSetFields{$_} } $self->newUserSet->FIELDS;
 1824 
 1825   $WeBWorK::timer->continue("DB: merge start") if defined($WeBWorK::timer);
 1826   for (my $i = 0; $i < @UserSets; $i++) {
 1827     my $UserSet = $UserSets[$i];
 1828     my $GlobalSet = $GlobalSets[$i];
 1829     next unless defined $UserSet and defined $GlobalSet;
 1830     foreach my $field (@commonFields) {
 1831       #next if defined $UserSet->$field;
 1832       # ok, now we're testing for emptiness as well as definedness.
 1833       next if defined $UserSet->$field and $UserSet->$field ne "";
 1834       $UserSet->$field($GlobalSet->$field);
 1835     }
 1836   }
 1837   $WeBWorK::timer->continue("DB: merge done!") if defined($WeBWorK::timer);
 1838 
 1839   return @UserSets;
 1840 }
 1841 
 1842 =back
 1843 
 1844 =cut
 1845 
 1846 ################################################################################
 1847 # problem+problem_user functions
 1848 ################################################################################
 1849 
 1850 =head2 Problem Merging Methods
 1851 
 1852 These functions combine a global problem and a user problem to create a merged
 1853 problem, which is returned. Any field that is not defined in the user problem is
 1854 taken from the global problem. Merged problems have the same type as user
 1855 problems.
 1856 
 1857 =over
 1858 
 1859 =cut
 1860 
 1861 sub getGlobalUserProblem {
 1862   carp "getGlobalUserProblem: this method is deprecated -- use getMergedProblem instead";
 1863   return shift->getMergedProblem(@_);
 1864 }
 1865 
 1866 =item getMergedProblem($userID, $setID, $problemID)
 1867 
 1868 Returns a merged problem record associated with the record IDs given. If there
 1869 is no record associated with a given record ID, the undefined value is returned.
 1870 
 1871 =cut
 1872 
 1873 sub getMergedProblem {
 1874   my ($self, $userID, $setID, $problemID) = @_;
 1875 
 1876   croak "getGlobalUserSet: requires 3 arguments"
 1877     unless @_ == 4;
 1878   croak "getGlobalUserSet: argument 1 must contain a user_id"
 1879     unless defined $userID;
 1880   croak "getGlobalUserSet: argument 2 must contain a set_id"
 1881     unless defined $setID;
 1882   croak "getGlobalUserSet: argument 3 must contain a problem_id"
 1883     unless defined $problemID;
 1884 
 1885   return ( $self->getMergedProblems([$userID, $setID, $problemID]) )[0];
 1886 }
 1887 
 1888 =item getMergedProblems(@userProblemIDs)
 1889 
 1890 Return a list of merged problem records associated with the record IDs given. If
 1891 there is no record associated with a given record ID, that element of the list
 1892 will be undefined. @userProblemIDs consists of references to arrays in which the
 1893 first element is the user_id, the second element is the set_id, and the third
 1894 element is the problem_id.
 1895 
 1896 =cut
 1897 
 1898 sub getMergedProblems {
 1899   my ($self, @userProblemIDs) = @_;
 1900 
 1901   #croak "getMergedProblems: requires 1 or more argument"
 1902   # unless @_ >= 2;
 1903   foreach my $i (0 .. $#userProblemIDs) {
 1904     croak "getMergedProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1905       unless defined $userProblemIDs[$i]
 1906              and ref $userProblemIDs[$i] eq "ARRAY"
 1907              and @{$userProblemIDs[$i]} == 3
 1908              and defined $userProblemIDs[$i]->[0]
 1909              and defined $userProblemIDs[$i]->[1]
 1910              and defined $userProblemIDs[$i]->[2];
 1911   }
 1912 
 1913   $WeBWorK::timer->continue("DB: getUserProblems start") if defined($WeBWorK::timer);
 1914   my @UserProblems = $self->getUserProblems(@userProblemIDs); # checked
 1915 
 1916   $WeBWorK::timer->continue("DB: pull out set/problem IDs start") if defined($WeBWorK::timer);
 1917   my @globalProblemIDs = map { [ $_->[1], $_->[2] ] } @userProblemIDs;
 1918   $WeBWorK::timer->continue("DB: getGlobalProblems start") if defined($WeBWorK::timer);
 1919   my @GlobalProblems = $self->getGlobalProblems(@globalProblemIDs); # checked
 1920 
 1921   $WeBWorK::timer->continue("DB: calc common fields start") if defined($WeBWorK::timer);
 1922   my %globalProblemFields = map { $_ => 1 } $self->newGlobalProblem->FIELDS;
 1923   my @commonFields = grep { exists $globalProblemFields{$_} } $self->newUserProblem->FIELDS;
 1924 
 1925   $WeBWorK::timer->continue("DB: merge start") if defined($WeBWorK::timer);
 1926   for (my $i = 0; $i < @UserProblems; $i++) {
 1927     my $UserProblem = $UserProblems[$i];
 1928     my $GlobalProblem = $GlobalProblems[$i];
 1929     next unless defined $UserProblem and defined $GlobalProblem;
 1930     foreach my $field (@commonFields) {
 1931       # FIXME: WW1Hash upgrades undefined fileds to "" when creating record objects
 1932       # Shouldn't we be testing for emptiness rather than definedness?
 1933       # I think the spec says that if a field is EMPTY the global value is used.
 1934       #next if defined $UserProblem->$field;
 1935       # ok, now we're testing for emptiness as well as definedness.
 1936       next if defined $UserProblem->$field and $UserProblem->$field ne "";
 1937       $UserProblem->$field($GlobalProblem->$field);
 1938     }
 1939   }
 1940   $WeBWorK::timer->continue("DB: merge done!") if defined($WeBWorK::timer);
 1941 
 1942   return @UserProblems;
 1943 }
 1944 
 1945 =back
 1946 
 1947 =cut
 1948 
 1949 ################################################################################
 1950 # debugging
 1951 ################################################################################
 1952 
 1953 #sub dumpDB($$) {
 1954 # my ($self, $table) = @_;
 1955 # return $self->{$table}->dumpDB();
 1956 #}
 1957 
 1958 ################################################################################
 1959 # utilities
 1960 ################################################################################
 1961 
 1962 sub checkKeyfields($) {
 1963   my ($Record) = @_;
 1964   foreach my $keyfield ($Record->KEYFIELDS) {
 1965     my $value = $Record->$keyfield;
 1966     croak "checkKeyfields: $keyfield is empty"
 1967       unless defined $value and $value ne "";
 1968 
 1969     if ($keyfield eq "problem_id") {
 1970       croak "checkKeyfields: invalid characters in $keyfield field: $value (valid characters are [0-9])"
 1971         unless $value =~ m/^\d*$/;
 1972     } else {
 1973       croak "checkKeyfields: invalid characters in $keyfield field: $value (valid characters are [A-Za-z0-9_])"
 1974         unless $value =~ m/^[\w-]*$/;
 1975     }
 1976   }
 1977 }
 1978 
 1979 =head1 AUTHOR
 1980 
 1981 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
 1982 
 1983 =cut
 1984 
 1985 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9