[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 2864 - (download) (as text) (annotate)
Thu Oct 7 01:39:45 2004 UTC (8 years, 7 months ago) by sh002i
File size: 59063 byte(s)
eliminate false positives in hashDatabaseOK()

    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.55 2004/09/23 18:45:48 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 ################################################################################
  798 # key functions
  799 ################################################################################
  800 
  801 =head2 Key Methods
  802 
  803 =over
  804 
  805 =item newKey()
  806 
  807 Returns a new, empty key object.
  808 
  809 =cut
  810 
  811 sub newKey {
  812   my ($self, @prototype) = @_;
  813   return $self->{key}->{record}->new(@prototype);
  814 }
  815 
  816 =item listKeys()
  817 
  818 Returns a list of user IDs representing the records in the key table.
  819 
  820 =cut
  821 
  822 sub listKeys($) {
  823   my ($self) = @_;
  824 
  825   croak "listKeys: requires 0 arguments"
  826     unless @_ == 1;
  827 
  828   return map { $_->[0] }
  829     $self->{key}->list(undef);
  830 }
  831 
  832 =item addKey($Key)
  833 
  834 $Key is a record object. The key will be added to the key table if a key with
  835 the same user ID does not already exist. If one does exist, an exception is
  836 thrown. To add a key, a user with a matching user ID must exist in the user
  837 table.
  838 
  839 =cut
  840 
  841 sub addKey($$) {
  842   my ($self, $Key) = @_;
  843 
  844   croak "addKey: requires 1 argument"
  845     unless @_ == 2;
  846   croak "addKey: argument 1 must be of type ", $self->{key}->{record}
  847     unless ref $Key eq $self->{key}->{record};
  848 
  849   checkKeyfields($Key);
  850 
  851   croak "addKey: key exists (perhaps you meant to use putKey?)"
  852     if $self->{key}->exists($Key->user_id);
  853   croak "addKey: user ", $Key->user_id, " not found"
  854     unless $self->{user}->exists($Key->user_id);
  855 
  856   return $self->{key}->add($Key);
  857 }
  858 
  859 =item getKey($userID)
  860 
  861 If a record with a matching user ID exists, a record object containting that
  862 record's data will be returned. If no such record exists, an undefined value
  863 will be returned.
  864 
  865 =cut
  866 
  867 sub getKey($$) {
  868   my ($self, $userID) = @_;
  869 
  870   croak "getKey: requires 1 argument"
  871     unless @_ == 2;
  872   croak "getKey: argument 1 must contain a user_id"
  873     unless defined $userID;
  874 
  875   return $self->{key}->get($userID);
  876 }
  877 
  878 =item getKeys(@uesrIDs)
  879 
  880 Return a list of key records associated with the user IDs given. If there is no
  881 record associated with a given user ID, that element of the list will be
  882 undefined.
  883 
  884 =cut
  885 
  886 sub getKeys {
  887   my ($self, @userIDs) = @_;
  888 
  889   #croak "getKeys: requires 1 or more argument"
  890   # unless @_ >= 2;
  891   foreach my $i (0 .. $#userIDs) {
  892     croak "getKeys: element $i of argument list must contain a user_id"
  893       unless defined $userIDs[$i];
  894   }
  895 
  896   return $self->{key}->gets(map { [$_] } @userIDs);
  897 }
  898 
  899 =item putKey($Key)
  900 
  901 $Key is a record object. If a key record with the same user ID exists in the
  902 key table, the data in the record is replaced with the data in $Key. If a
  903 matching key record does not exist, an exception is thrown.
  904 
  905 =cut
  906 
  907 sub putKey($$) {
  908   my ($self, $Key) = @_;
  909 
  910   croak "putKey: requires 1 argument"
  911     unless @_ == 2;
  912   croak "putKey: argument 1 must be of type ", $self->{key}->{record}
  913     unless ref $Key eq $self->{key}->{record};
  914 
  915   checkKeyfields($Key);
  916 
  917   croak "putKey: key not found (perhaps you meant to use addKey?)"
  918     unless $self->{key}->exists($Key->user_id);
  919 
  920   return $self->{key}->put($Key);
  921 }
  922 
  923 =item deleteKey($userID)
  924 
  925 If a key record with a user ID matching $userID exists in the key table, it is
  926 removed and the method returns a true value. If one does exist, a false value
  927 is returned.
  928 
  929 =cut
  930 
  931 sub deleteKey($$) {
  932   my ($self, $userID) = @_;
  933 
  934   croak "deleteKey: requires 1 argument"
  935     unless @_ == 2;
  936   croak "deleteKey: argument 1 must contain a user_id"
  937     unless defined $userID;
  938 
  939   return $self->{key}->delete($userID);
  940 }
  941 
  942 ################################################################################
  943 # user functions
  944 ################################################################################
  945 
  946 =head2 User Methods
  947 
  948 =over
  949 
  950 =item newUser()
  951 
  952 Returns a new, empty user object.
  953 
  954 =cut
  955 
  956 sub newUser {
  957   my ($self, @prototype) = @_;
  958   return $self->{user}->{record}->new(@prototype);
  959 }
  960 
  961 =item listUsers()
  962 
  963 Returns a list of user IDs representing the records in the user table.
  964 
  965 =cut
  966 
  967 sub listUsers {
  968   my ($self) = @_;
  969 
  970   croak "listUsers: requires 0 arguments"
  971     unless @_ == 1;
  972 
  973   return map { $_->[0] }
  974     $self->{user}->list(undef);
  975 }
  976 
  977 =item addUser($User)
  978 
  979 $User is a record object. The user will be added to the user table if a user
  980 with the same user ID does not already exist. If one does exist, an exception
  981 is thrown.
  982 
  983 =cut
  984 
  985 sub addUser {
  986   my ($self, $User) = @_;
  987 
  988   croak "addUser: requires 1 argument"
  989     unless @_ == 2;
  990   croak "addUser: argument 1 must be of type ", $self->{user}->{record}
  991     unless ref $User eq $self->{user}->{record};
  992 
  993   checkKeyfields($User);
  994 
  995   croak "addUser: user exists (perhaps you meant to use putUser?)"
  996     if $self->{user}->exists($User->user_id);
  997 
  998   return $self->{user}->add($User);
  999 }
 1000 
 1001 =item getUser($userID)
 1002 
 1003 If a record with a matching user ID exists, a record object containting that
 1004 record's data will be returned. If no such record exists, an undefined value
 1005 will be returned.
 1006 
 1007 =cut
 1008 
 1009 sub getUser {
 1010   my ($self, $userID) = @_;
 1011 
 1012   croak "getUser: requires 1 argument"
 1013     unless @_ == 2;
 1014   croak "getUser: argument 1 must contain a user_id"
 1015     unless defined $userID;
 1016 
 1017   return $self->{user}->get($userID);
 1018 }
 1019 
 1020 =item getUsers(@uesrIDs)
 1021 
 1022 Return a list of user records associated with the user IDs given. If there is no
 1023 record associated with a given user ID, that element of the list will be
 1024 undefined.
 1025 
 1026 =cut
 1027 
 1028 sub getUsers {
 1029   my ($self, @userIDs) = @_;
 1030 
 1031   #croak "getUsers: requires 1 or more argument"
 1032   # unless @_ >= 2;
 1033   foreach my $i (0 .. $#userIDs) {
 1034     croak "getUsers: element $i of argument list must contain a user_id"
 1035       unless defined $userIDs[$i];
 1036   }
 1037 
 1038   return $self->{user}->gets(map { [$_] } @userIDs);
 1039 }
 1040 
 1041 =item putUser($User)
 1042 
 1043 $User is a record object. If a user record with the same user ID exists in the
 1044 user table, the data in the record is replaced with the data in $User. If a
 1045 matching user record does not exist, an exception is thrown.
 1046 
 1047 =cut
 1048 
 1049 sub putUser {
 1050   my ($self, $User) = @_;
 1051 
 1052   croak "putUser: requires 1 argument"
 1053     unless @_ == 2;
 1054   croak "putUser: argument 1 must be of type ", $self->{user}->{record}
 1055     unless ref $User eq $self->{user}->{record};
 1056 
 1057   checkKeyfields($User);
 1058 
 1059   croak "putUser: user not found (perhaps you meant to use addUser?)"
 1060     unless $self->{user}->exists($User->user_id);
 1061 
 1062   return $self->{user}->put($User);
 1063 }
 1064 
 1065 =item deleteUser($userID)
 1066 
 1067 If a user record with a user ID matching $userID exists in the user table, it
 1068 is removed and the method returns a true value. If one does exist, a false
 1069 value is returned. When a user record is deleted, all records associated with
 1070 that user are also deleted. This includes the password, permission, and key
 1071 records, and all user set records for that user.
 1072 
 1073 =cut
 1074 
 1075 sub deleteUser {
 1076   my ($self, $userID) = @_;
 1077 
 1078   croak "deleteUser: requires 1 argument"
 1079     unless @_ == 2;
 1080   croak "deleteUser: argument 1 must contain a user_id"
 1081     unless defined $userID;
 1082 
 1083   $self->deleteUserSet($userID, undef);
 1084   $self->deletePassword($userID);
 1085   $self->deletePermissionLevel($userID);
 1086   $self->deleteKey($userID);
 1087   return $self->{user}->delete($userID);
 1088 }
 1089 
 1090 =back
 1091 
 1092 =cut
 1093 
 1094 ################################################################################
 1095 # set functions
 1096 ################################################################################
 1097 
 1098 =head2 Global Set Methods
 1099 
 1100 FIXME: write this
 1101 
 1102 =over
 1103 
 1104 =cut
 1105 
 1106 =item newGlobalSet()
 1107 
 1108 =cut
 1109 
 1110 sub newGlobalSet {
 1111   my ($self, @prototype) = @_;
 1112   return $self->{set}->{record}->new(@prototype);
 1113 }
 1114 
 1115 =item listGlobalSets()
 1116 
 1117 =cut
 1118 
 1119 sub listGlobalSets {
 1120   my ($self) = @_;
 1121 
 1122   croak "listGlobalSets: requires 0 arguments"
 1123     unless @_ == 1;
 1124 
 1125   return map { $_->[0] }
 1126     $self->{set}->list(undef);
 1127 }
 1128 
 1129 =item addGlobalSet($GlobalSet)
 1130 
 1131 =cut
 1132 
 1133 sub addGlobalSet {
 1134   my ($self, $GlobalSet) = @_;
 1135 
 1136   croak "addGlobalSet: requires 1 argument"
 1137     unless @_ == 2;
 1138   croak "addGlobalSet: argument 1 must be of type ", $self->{set}->{record}
 1139     unless ref $GlobalSet eq $self->{set}->{record};
 1140 
 1141   checkKeyfields($GlobalSet);
 1142 
 1143   croak "addGlobalSet: global set exists (perhaps you meant to use putGlobalSet?)"
 1144     if $self->{set}->exists($GlobalSet->set_id);
 1145 
 1146   return $self->{set}->add($GlobalSet);
 1147 }
 1148 
 1149 =item addGlobalSet($setID)
 1150 
 1151 =cut
 1152 
 1153 sub getGlobalSet {
 1154   my ($self, $setID) = @_;
 1155 
 1156   croak "getGlobalSet: requires 1 argument"
 1157     unless @_ == 2;
 1158   croak "getGlobalSet: argument 1 must contain a set_id"
 1159     unless defined $setID;
 1160 
 1161   return $self->{set}->get($setID);
 1162 }
 1163 
 1164 =item getGlobalSets(@setIDs)
 1165 
 1166 Return a list of global set records associated with the record IDs given. If
 1167 there is no record associated with a given record ID, that element of the list
 1168 will be undefined.
 1169 
 1170 =cut
 1171 
 1172 sub getGlobalSets {
 1173   my ($self, @setIDs) = @_;
 1174 
 1175   #croak "getGlobalSets: requires 1 or more argument"
 1176   # unless @_ >= 2;
 1177   foreach my $i (0 .. $#setIDs) {
 1178     croak "getGlobalSets: element $i of argument list must contain a set_id"
 1179       unless defined $setIDs[$i];
 1180   }
 1181 
 1182   return $self->{set}->gets(map { [$_] } @setIDs);
 1183 }
 1184 
 1185 =item addGlobalSet($GlobalSet)
 1186 
 1187 =cut
 1188 
 1189 sub putGlobalSet {
 1190   my ($self, $GlobalSet) = @_;
 1191 
 1192   croak "putGlobalSet: requires 1 argument"
 1193     unless @_ == 2;
 1194   croak "putGlobalSet: argument 1 must be of type ", $self->{set}->{record}
 1195     unless ref $GlobalSet eq $self->{set}->{record};
 1196 
 1197   checkKeyfields($GlobalSet);
 1198 
 1199   croak "putGlobalSet: global set not found (perhaps you meant to use addGlobalSet?)"
 1200     unless $self->{set}->exists($GlobalSet->set_id);
 1201 
 1202   return $self->{set}->put($GlobalSet);
 1203 }
 1204 
 1205 =item addGlobalSet($setID)
 1206 
 1207 =cut
 1208 
 1209 sub deleteGlobalSet {
 1210   my ($self, $setID) = @_;
 1211 
 1212   croak "deleteGlobalSet: requires 1 argument"
 1213     unless @_ == 2;
 1214   croak "deleteGlobalSet: argument 1 must contain a set_id"
 1215     unless defined $setID or caller eq __PACKAGE__;
 1216 
 1217   $self->deleteUserSet(undef, $setID);
 1218   $self->deleteGlobalProblem($setID, undef);
 1219   return $self->{set}->delete($setID);
 1220 }
 1221 
 1222 =back
 1223 
 1224 =cut
 1225 
 1226 ################################################################################
 1227 # set_user functions
 1228 ################################################################################
 1229 
 1230 =head2 User-Specific Set Methods
 1231 
 1232 FIXME: write this
 1233 
 1234 =over
 1235 
 1236 =cut
 1237 
 1238 sub newUserSet {
 1239   my ($self, @prototype) = @_;
 1240   return $self->{set_user}->{record}->new(@prototype);
 1241 }
 1242 
 1243 sub countSetUsers {
 1244   my ($self, $setID) = @_;
 1245 
 1246   croak "countSetUsers: requires 1 argument"
 1247     unless @_ == 2;
 1248   croak "countSetUsers: argument 1 must contain a set_id"
 1249     unless defined $setID;
 1250 
 1251   # inefficient way
 1252   #return scalar $self->{set_user}->list(undef, $setID);
 1253 
 1254   # efficient way
 1255   return $self->{set_user}->count(undef, $setID);
 1256 }
 1257 
 1258 sub listSetUsers {
 1259   my ($self, $setID) = @_;
 1260 
 1261   carp "listSetUsers called in SCALAR context: use countSetUsers instead!\n"
 1262     unless wantarray;
 1263 
 1264   croak "listSetUsers: requires 1 argument"
 1265     unless @_ == 2;
 1266   croak "listSetUsers: argument 1 must contain a set_id"
 1267     unless defined $setID;
 1268 
 1269   return map { $_->[0] } # extract user_id
 1270     $self->{set_user}->list(undef, $setID);
 1271 }
 1272 
 1273 sub countUserSets {
 1274   my ($self, $userID) = @_;
 1275 
 1276   croak "countUserSets: requires 1 argument"
 1277     unless @_ == 2;
 1278   croak "countUserSets: argument 1 must contain a user_id"
 1279     unless defined $userID;
 1280 
 1281   return $self->{set_user}->count($userID, undef);
 1282 }
 1283 
 1284 sub listUserSets {
 1285   my ($self, $userID) = @_;
 1286 
 1287   croak "listUserSets: requires 1 argument"
 1288     unless @_ == 2;
 1289   croak "listUserSets: argument 1 must contain a user_id"
 1290     unless defined $userID;
 1291 
 1292   return map { $_->[1] } # extract set_id
 1293     $self->{set_user}->list($userID, undef);
 1294 }
 1295 
 1296 sub addUserSet {
 1297   my ($self, $UserSet) = @_;
 1298 
 1299   croak "addUserSet: requires 1 argument"
 1300     unless @_ == 2;
 1301   croak "addUserSet: argument 1 must be of type ", $self->{set_user}->{record}
 1302     unless ref $UserSet eq $self->{set_user}->{record};
 1303 
 1304   checkKeyfields($UserSet);
 1305 
 1306   croak "addUserSet: user set exists (perhaps you meant to use putUserSet?)"
 1307     if $self->{set_user}->exists($UserSet->user_id, $UserSet->set_id);
 1308   croak "addUserSet: user ", $UserSet->user_id, " not found"
 1309     unless $self->{user}->exists($UserSet->user_id);
 1310   croak "addUserSet: set ", $UserSet->set_id, " not found"
 1311     unless $self->{set}->exists($UserSet->set_id);
 1312 
 1313   return $self->{set_user}->add($UserSet);
 1314 }
 1315 
 1316 sub getUserSet {
 1317   my ($self, $userID, $setID) = @_;
 1318 
 1319   croak "getUserSet: requires 2 arguments"
 1320     unless @_ == 3;
 1321   croak "getUserSet: argument 1 must contain a user_id"
 1322     unless defined $userID;
 1323   croak "getUserSet: argument 2 must contain a set_id"
 1324     unless defined $setID;
 1325 
 1326   #return $self->{set_user}->get($userID, $setID);
 1327   return ( $self->getUserSets([$userID, $setID]) )[0];
 1328 }
 1329 
 1330 =item getUserSets(@userSetIDs)
 1331 
 1332 Return a list of user set records associated with the record IDs given. If there
 1333 is no record associated with a given record ID, that element of the list will be
 1334 undefined. @userProblemIDs consists of references to arrays in which the first
 1335 element is the user_id and the second element is the set_id.
 1336 
 1337 =cut
 1338 
 1339 sub getUserSets {
 1340   my ($self, @userSetIDs) = @_;
 1341 
 1342   #croak "getUserSets: requires 1 or more argument"
 1343   # unless @_ >= 2;
 1344   foreach my $i (0 .. $#userSetIDs) {
 1345     croak "getUserSets: element $i of argument list must contain a <user_id, set_id> pair"
 1346       unless defined $userSetIDs[$i]
 1347              and ref $userSetIDs[$i] eq "ARRAY"
 1348              and @{$userSetIDs[$i]} == 2
 1349              and defined $userSetIDs[$i]->[0]
 1350              and defined $userSetIDs[$i]->[1];
 1351   }
 1352 
 1353   return $self->{set_user}->gets(@userSetIDs);
 1354 }
 1355 
 1356 sub putUserSet {
 1357   my ($self, $UserSet) = @_;
 1358 
 1359   croak "putUserSet: requires 1 argument"
 1360     unless @_ == 2;
 1361   croak "putUserSet: argument 1 must be of type ", $self->{set_user}->{record}
 1362     unless ref $UserSet eq $self->{set_user}->{record};
 1363 
 1364   checkKeyfields($UserSet);
 1365 
 1366   croak "putUserSet: user set not found (perhaps you meant to use addUserSet?)"
 1367     unless $self->{set_user}->exists($UserSet->user_id, $UserSet->set_id);
 1368   croak "putUserSet: user ", $UserSet->user_id, " not found"
 1369     unless $self->{user}->exists($UserSet->user_id);
 1370   croak "putUserSet: set ", $UserSet->set_id, " not found"
 1371     unless $self->{set}->exists($UserSet->set_id);
 1372 
 1373   return $self->{set_user}->put($UserSet);
 1374 }
 1375 
 1376 sub deleteUserSet {
 1377   my ($self, $userID, $setID) = @_;
 1378 
 1379   croak "getUserSet: requires 2 arguments"
 1380     unless @_ == 3;
 1381   croak "getUserSet: argument 1 must contain a user_id"
 1382     unless defined $userID or caller eq __PACKAGE__;
 1383   croak "getUserSet: argument 2 must contain a set_id"
 1384     unless defined $userID or caller eq __PACKAGE__;
 1385 
 1386   $self->deleteUserProblem($userID, $setID, undef);
 1387   return $self->{set_user}->delete($userID, $setID);
 1388 }
 1389 
 1390 =back
 1391 
 1392 =cut
 1393 
 1394 ################################################################################
 1395 # problem functions
 1396 ################################################################################
 1397 
 1398 =head2 Global Problem Methods
 1399 
 1400 FIXME: write this
 1401 
 1402 =over
 1403 
 1404 =cut
 1405 
 1406 sub newGlobalProblem {
 1407   my ($self, @prototype) = @_;
 1408   return $self->{problem}->{record}->new(@prototype);
 1409 }
 1410 
 1411 sub listGlobalProblems {
 1412   my ($self, $setID) = @_;
 1413 
 1414   croak "listGlobalProblems: requires 1 arguments"
 1415     unless @_ == 2;
 1416   croak "listGlobalProblems: argument 1 must contain a set_id"
 1417     unless defined $setID;
 1418 
 1419   return map { $_->[1] }
 1420     $self->{problem}->list($setID, undef);
 1421 }
 1422 
 1423 sub addGlobalProblem {
 1424   my ($self, $GlobalProblem) = @_;
 1425 
 1426   croak "addGlobalProblem: requires 1 argument"
 1427     unless @_ == 2;
 1428   croak "addGlobalProblem: argument 1 must be of type ", $self->{problem}->{record}
 1429     unless ref $GlobalProblem eq $self->{problem}->{record};
 1430 
 1431   checkKeyfields($GlobalProblem);
 1432 
 1433   croak "addGlobalProblem: global problem exists (perhaps you meant to use putGlobalProblem?)"
 1434     if $self->{problem}->exists($GlobalProblem->set_id, $GlobalProblem->problem_id);
 1435   croak "addGlobalProblem: set ", $GlobalProblem->set_id, " not found"
 1436     unless $self->{set}->exists($GlobalProblem->set_id);
 1437 
 1438   return $self->{problem}->add($GlobalProblem);
 1439 }
 1440 
 1441 sub getGlobalProblem {
 1442   my ($self, $setID, $problemID) = @_;
 1443 
 1444   croak "getGlobalProblem: requires 2 arguments"
 1445     unless @_ == 3;
 1446   croak "getGlobalProblem: argument 1 must contain a set_id"
 1447     unless defined $setID;
 1448   croak "getGlobalProblem: argument 2 must contain a problem_id"
 1449     unless defined $problemID;
 1450 
 1451   return $self->{problem}->get($setID, $problemID);
 1452 }
 1453 
 1454 =item getGlobalProblems(@problemIDs)
 1455 
 1456 Return a list of global set records associated with the record IDs given. If
 1457 there is no record associated with a given record ID, that element of the list
 1458 will be undefined. @problemIDs consists of references to arrays in which the
 1459 first element is the set_id, and the second element is the problem_id.
 1460 
 1461 =cut
 1462 
 1463 sub getGlobalProblems {
 1464   my ($self, @problemIDs) = @_;
 1465 
 1466   #croak "getGlobalProblems: requires 1 or more argument"
 1467   # unless @_ >= 2;
 1468   foreach my $i (0 .. $#problemIDs) {
 1469     croak "getUserSets: element $i of argument list must contain a <set_id, problem_id> pair"
 1470       unless defined $problemIDs[$i]
 1471              and ref $problemIDs[$i] eq "ARRAY"
 1472              and @{$problemIDs[$i]} == 2
 1473              and defined $problemIDs[$i]->[0]
 1474              and defined $problemIDs[$i]->[1];
 1475   }
 1476 
 1477   return $self->{problem}->gets(@problemIDs);
 1478 }
 1479 
 1480 =item getAllGlobalProblems($setID)
 1481 
 1482 Returns a list of Problem objects representing all the problems in the given
 1483 global set. When using the WW1Hash/GlobalTableEmulator schemas, this is far
 1484 more efficient than using listGlobalProblems and getGlobalProblems.
 1485 
 1486 =cut
 1487 
 1488 sub getAllGlobalProblems {
 1489   my ($self, $setID) = @_;
 1490 
 1491   croak "getAllGlobalProblems: requires 1 arguments"
 1492     unless @_ == 2;
 1493   croak "getAllGlobalProblems: argument 1 must contain a set_id"
 1494     unless defined $setID;
 1495 
 1496   if ($self->{problem}->can("getAll")) {
 1497     return $self->{problem}->getAll($setID);
 1498   } else {
 1499     my @problemIDPairs = $self->{problem}->list($setID, undef);
 1500     return $self->{problem}->gets(@problemIDPairs);
 1501   }
 1502 }
 1503 
 1504 sub putGlobalProblem {
 1505   my ($self, $GlobalProblem) = @_;
 1506 
 1507   croak "putGlobalProblem: requires 1 argument"
 1508     unless @_ == 2;
 1509   croak "putGlobalProblem: argument 1 must be of type ", $self->{problem}->{record}
 1510     unless ref $GlobalProblem eq $self->{problem}->{record};
 1511 
 1512   checkKeyfields($GlobalProblem);
 1513 
 1514   croak "putGlobalProblem: global problem not found (perhaps you meant to use addGlobalProblem?)"
 1515     unless $self->{problem}->exists($GlobalProblem->set_id, $GlobalProblem->problem_id);
 1516   croak "putGlobalProblem: set ", $GlobalProblem->set_id, " not found"
 1517     unless $self->{set}->exists($GlobalProblem->set_id);
 1518 
 1519   return $self->{problem}->put($GlobalProblem);
 1520 }
 1521 
 1522 sub deleteGlobalProblem {
 1523   my ($self, $setID, $problemID) = @_;
 1524 
 1525   croak "deleteGlobalProblem: requires 2 arguments"
 1526     unless @_ == 3;
 1527   croak "deleteGlobalProblem: argument 1 must contain a set_id"
 1528     unless defined $setID or caller eq __PACKAGE__;
 1529   croak "deleteGlobalProblem: argument 2 must contain a problem_id"
 1530     unless defined $problemID or caller eq __PACKAGE__;
 1531 
 1532   $self->deleteUserProblem(undef, $setID, $problemID);
 1533   return $self->{problem}->delete($setID, $problemID);
 1534 }
 1535 
 1536 =back
 1537 
 1538 =cut
 1539 
 1540 ################################################################################
 1541 # problem_user functions
 1542 ################################################################################
 1543 
 1544 =head2 User-Specific Problem Methods
 1545 
 1546 FIXME: write this
 1547 
 1548 =over
 1549 
 1550 =cut
 1551 
 1552 sub newUserProblem {
 1553   my ($self, @prototype) = @_;
 1554   return $self->{problem_user}->{record}->new(@prototype);
 1555 }
 1556 
 1557 sub countProblemUsers {
 1558   my ($self, $setID, $problemID) = @_;
 1559 
 1560   croak "countProblemUsers: requires 2 arguments"
 1561     unless @_ == 3;
 1562   croak "countProblemUsers: argument 1 must contain a set_id"
 1563     unless defined $setID;
 1564   croak "countProblemUsers: argument 2 must contain a problem_id"
 1565     unless defined $problemID;
 1566 
 1567   # the slow way
 1568   #return scalar $self->{problem_user}->list(undef, $setID, $problemID);
 1569 
 1570   # the fast way
 1571   return $self->{problem_user}->count(undef, $setID, $problemID);
 1572 }
 1573 
 1574 sub listProblemUsers {
 1575   my ($self, $setID, $problemID) = @_;
 1576 
 1577   carp "listProblemUsers called in SCALAR context: use countProblemUsers instead!\n"
 1578     unless wantarray;
 1579 
 1580   croak "listProblemUsers: requires 2 arguments"
 1581     unless @_ == 3;
 1582   croak "listProblemUsers: argument 1 must contain a set_id"
 1583     unless defined $setID;
 1584   croak "listProblemUsers: argument 2 must contain a problem_id"
 1585     unless defined $problemID;
 1586 
 1587   return map { $_->[0] } # extract user_id
 1588     $self->{problem_user}->list(undef, $setID, $problemID);
 1589 }
 1590 
 1591 sub listUserProblems {
 1592   my ($self, $userID, $setID) = @_;
 1593 
 1594   croak "listUserProblems: requires 2 arguments"
 1595     unless @_ == 3;
 1596   croak "listUserProblems: argument 1 must contain a user_id"
 1597     unless defined $userID;
 1598   croak "listUserProblems: argument 2 must contain a set_id"
 1599     unless defined $setID;
 1600 
 1601   return map { $_->[2] } # extract problem_id
 1602     $self->{problem_user}->list($userID, $setID, undef);
 1603 }
 1604 
 1605 sub addUserProblem {
 1606   my ($self, $UserProblem) = @_;
 1607 
 1608   croak "addUserProblem: requires 1 argument"
 1609     unless @_ == 2;
 1610   croak "addUserProblem: argument 1 must be of type ", $self->{problem_user}->{record}
 1611     unless ref $UserProblem eq $self->{problem_user}->{record};
 1612 
 1613   checkKeyfields($UserProblem);
 1614 
 1615   croak "addUserProblem: user problem exists (perhaps you meant to use putUserProblem?)"
 1616     if $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
 1617   croak "addUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
 1618     unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
 1619   croak "addUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
 1620     unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
 1621 
 1622   return $self->{problem_user}->add($UserProblem);
 1623 }
 1624 
 1625 sub getUserProblem {
 1626   my ($self, $userID, $setID, $problemID) = @_;
 1627 
 1628   croak "getUserProblem: requires 3 arguments"
 1629     unless @_ == 4;
 1630   croak "getUserProblem: argument 1 must contain a user_id"
 1631     unless defined $userID;
 1632   croak "getUserProblem: argument 2 must contain a set_id"
 1633     unless defined $setID;
 1634   croak "getUserProblem: argument 3 must contain a problem_id"
 1635     unless defined $problemID;
 1636 
 1637   return ( $self->getUserProblems([$userID, $setID, $problemID]) )[0];
 1638 }
 1639 
 1640 =item getUserProblems(@userProblemIDs)
 1641 
 1642 Return a list of user set records associated with the user IDs given. If there
 1643 is no record associated with a given user ID, that element of the list will be
 1644 undefined. @userProblemIDs consists of references to arrays in which the first
 1645 element is the user_id, the second element is the set_id, and the third element
 1646 is the problem_id.
 1647 
 1648 =cut
 1649 
 1650 sub getUserProblems {
 1651   my ($self, @userProblemIDs) = @_;
 1652 
 1653   #croak "getUserProblems: requires 1 or more argument"
 1654   # unless @_ >= 2;
 1655   foreach my $i (0 .. $#userProblemIDs) {
 1656     croak "getUserProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1657       unless defined $userProblemIDs[$i]
 1658              and ref $userProblemIDs[$i] eq "ARRAY"
 1659              and @{$userProblemIDs[$i]} == 3
 1660              and defined $userProblemIDs[$i]->[0]
 1661              and defined $userProblemIDs[$i]->[1]
 1662              and defined $userProblemIDs[$i]->[2];
 1663   }
 1664 
 1665   return $self->{problem_user}->gets(@userProblemIDs);
 1666 }
 1667 
 1668 =item getAllUserProblems($userID, $setID)
 1669 
 1670 Returns a list of UserProblem objects representing all the problems in the
 1671 given set. When using the WW1Hash/GlobalTableEmulator schemas, this is far
 1672 more efficient than using listUserProblems and getUserProblems.
 1673 
 1674 =cut
 1675 
 1676 sub getAllUserProblems {
 1677   my ($self, $userID, $setID) = @_;
 1678 
 1679   croak "getAllUserProblems: requires 2 arguments"
 1680     unless @_ == 3;
 1681   croak "getAllUserProblems: argument 1 must contain a user_id"
 1682     unless defined $userID;
 1683   croak "getAllUserProblems: argument 2 must contain a set_id"
 1684     unless defined $setID;
 1685 
 1686   if ($self->{problem_user}->can("getAll")) {
 1687     return $self->{problem_user}->getAll($userID, $setID);
 1688   } else {
 1689     my @problemIDTriples = $self->{problem_user}->list($userID, $setID, undef);
 1690     return $self->{problem_user}->gets(@problemIDTriples);
 1691   }
 1692 }
 1693 
 1694 sub putUserProblem {
 1695   my ($self, $UserProblem) = @_;
 1696 
 1697   croak "putUserProblem: requires 1 argument"
 1698     unless @_ == 2;
 1699   croak "putUserProblem: argument 1 must be of type ", $self->{problem_user}->{record}
 1700     unless ref $UserProblem eq $self->{problem_user}->{record};
 1701 
 1702   checkKeyfields($UserProblem);
 1703 
 1704   croak "putUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
 1705     unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
 1706   croak "putUserProblem: user problem not found (perhaps you meant to use addUserProblem?)"
 1707     unless $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
 1708   croak "putUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
 1709     unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
 1710 
 1711   return $self->{problem_user}->put($UserProblem);
 1712 }
 1713 
 1714 sub deleteUserProblem {
 1715   my ($self, $userID, $setID, $problemID) = @_;
 1716 
 1717   croak "getUserProblem: requires 3 arguments"
 1718     unless @_ == 4;
 1719   croak "getUserProblem: argument 1 must contain a user_id"
 1720     unless defined $userID or caller eq __PACKAGE__;
 1721   croak "getUserProblem: argument 2 must contain a set_id"
 1722     unless defined $setID or caller eq __PACKAGE__;
 1723   croak "getUserProblem: argument 3 must contain a problem_id"
 1724     unless defined $problemID or caller eq __PACKAGE__;
 1725 
 1726   return $self->{problem_user}->delete($userID, $setID, $problemID);
 1727 }
 1728 
 1729 =back
 1730 
 1731 =cut
 1732 
 1733 ################################################################################
 1734 # set+set_user functions
 1735 ################################################################################
 1736 
 1737 =head2 Set Merging Methods
 1738 
 1739 These functions combine a global set and a user set to create a merged set,
 1740 which is returned. Any field that is not defined in the user set is taken from
 1741 the global set. Merged sets have the same type as user sets.
 1742 
 1743 =over
 1744 
 1745 =cut
 1746 
 1747 sub getGlobalUserSet {
 1748   carp "getGlobalUserSet: this method is deprecated -- use getMergedSet instead";
 1749   return shift->getMergedSet(@_);
 1750 }
 1751 
 1752 =item getMergedSet($userID, $setID)
 1753 
 1754 Returns a merged set record associated with the record IDs given. If there is no
 1755 record associated with a given record ID, the undefined value is returned.
 1756 
 1757 =cut
 1758 
 1759 sub getMergedSet {
 1760   my ($self, $userID, $setID) = @_;
 1761 
 1762   croak "getMergedSet: requires 2 arguments"
 1763     unless @_ == 3;
 1764   croak "getMergedSet: argument 1 must contain a user_id"
 1765     unless defined $userID;
 1766   croak "getMergedSet: argument 2 must contain a set_id"
 1767     unless defined $setID;
 1768 
 1769   return ( $self->getMergedSets([$userID, $setID]) )[0];
 1770 }
 1771 
 1772 =item getMegedSets(@userSetIDs)
 1773 
 1774 Return a list of merged set records associated with the record IDs given. If
 1775 there is no record associated with a given record ID, that element of the list
 1776 will be undefined. @userSetIDs consists of references to arrays in which the
 1777 first element is the user_id and the second element is the set_id.
 1778 
 1779 =cut
 1780 
 1781 sub getMergedSets {
 1782   my ($self, @userSetIDs) = @_;
 1783 
 1784   #croak "getMergedSets: requires 1 or more argument"
 1785   # unless @_ >= 2;
 1786   foreach my $i (0 .. $#userSetIDs) {
 1787     croak "getMergedSets: element $i of argument list must contain a <user_id, set_id> pair"
 1788       unless defined $userSetIDs[$i]
 1789              and ref $userSetIDs[$i] eq "ARRAY"
 1790              and @{$userSetIDs[$i]} == 2
 1791              and defined $userSetIDs[$i]->[0]
 1792              and defined $userSetIDs[$i]->[1];
 1793   }
 1794 
 1795   # a horrible, terrible hack ;)
 1796   if (ref $self->{set_user} eq "WeBWorK::DB::Schema::WW1Hash"
 1797       and ref $self->{set} eq "WeBWorK::DB::Schema::GlobalTableEmulator") {
 1798     #warn __PACKAGE__.": using a terrible hack.\n";
 1799     $WeBWorK::timer->continue("DB: getsNoFilter start") if defined($WeBWorK::timer);
 1800     my @MergedSets = $self->{set_user}->getsNoFilter(@userSetIDs);
 1801     $WeBWorK::timer->continue("DB: getsNoFilter end") if defined($WeBWorK::timer);
 1802     return @MergedSets;
 1803   }
 1804 
 1805   $WeBWorK::timer->continue("DB: getUserSets start") if defined($WeBWorK::timer);
 1806   my @UserSets = $self->getUserSets(@userSetIDs); # checked
 1807 
 1808   $WeBWorK::timer->continue("DB: pull out set IDs start") if defined($WeBWorK::timer);
 1809   my @globalSetIDs = map { $_->[1] } @userSetIDs;
 1810   $WeBWorK::timer->continue("DB: getGlobalSets start") if defined($WeBWorK::timer);
 1811   my @GlobalSets = $self->getGlobalSets(@globalSetIDs); # checked
 1812 
 1813   $WeBWorK::timer->continue("DB: calc common fields start") if defined($WeBWorK::timer);
 1814   my %globalSetFields = map { $_ => 1 } $self->newGlobalSet->FIELDS;
 1815   my @commonFields = grep { exists $globalSetFields{$_} } $self->newUserSet->FIELDS;
 1816 
 1817   $WeBWorK::timer->continue("DB: merge start") if defined($WeBWorK::timer);
 1818   for (my $i = 0; $i < @UserSets; $i++) {
 1819     my $UserSet = $UserSets[$i];
 1820     my $GlobalSet = $GlobalSets[$i];
 1821     next unless defined $UserSet and defined $GlobalSet;
 1822     foreach my $field (@commonFields) {
 1823       #next if defined $UserSet->$field;
 1824       # ok, now we're testing for emptiness as well as definedness.
 1825       next if defined $UserSet->$field and $UserSet->$field ne "";
 1826       $UserSet->$field($GlobalSet->$field);
 1827     }
 1828   }
 1829   $WeBWorK::timer->continue("DB: merge done!") if defined($WeBWorK::timer);
 1830 
 1831   return @UserSets;
 1832 }
 1833 
 1834 =back
 1835 
 1836 =cut
 1837 
 1838 ################################################################################
 1839 # problem+problem_user functions
 1840 ################################################################################
 1841 
 1842 =head2 Problem Merging Methods
 1843 
 1844 These functions combine a global problem and a user problem to create a merged
 1845 problem, which is returned. Any field that is not defined in the user problem is
 1846 taken from the global problem. Merged problems have the same type as user
 1847 problems.
 1848 
 1849 =over
 1850 
 1851 =cut
 1852 
 1853 sub getGlobalUserProblem {
 1854   carp "getGlobalUserProblem: this method is deprecated -- use getMergedProblem instead";
 1855   return shift->getMergedProblem(@_);
 1856 }
 1857 
 1858 =item getMergedProblem($userID, $setID, $problemID)
 1859 
 1860 Returns a merged problem record associated with the record IDs given. If there
 1861 is no record associated with a given record ID, the undefined value is returned.
 1862 
 1863 =cut
 1864 
 1865 sub getMergedProblem {
 1866   my ($self, $userID, $setID, $problemID) = @_;
 1867 
 1868   croak "getGlobalUserSet: requires 3 arguments"
 1869     unless @_ == 4;
 1870   croak "getGlobalUserSet: argument 1 must contain a user_id"
 1871     unless defined $userID;
 1872   croak "getGlobalUserSet: argument 2 must contain a set_id"
 1873     unless defined $setID;
 1874   croak "getGlobalUserSet: argument 3 must contain a problem_id"
 1875     unless defined $problemID;
 1876 
 1877   return ( $self->getMergedProblems([$userID, $setID, $problemID]) )[0];
 1878 }
 1879 
 1880 =item getMergedProblems(@userProblemIDs)
 1881 
 1882 Return a list of merged problem records associated with the record IDs given. If
 1883 there is no record associated with a given record ID, that element of the list
 1884 will be undefined. @userProblemIDs consists of references to arrays in which the
 1885 first element is the user_id, the second element is the set_id, and the third
 1886 element is the problem_id.
 1887 
 1888 =cut
 1889 
 1890 sub getMergedProblems {
 1891   my ($self, @userProblemIDs) = @_;
 1892 
 1893   #croak "getMergedProblems: requires 1 or more argument"
 1894   # unless @_ >= 2;
 1895   foreach my $i (0 .. $#userProblemIDs) {
 1896     croak "getMergedProblems: element $i of argument list must contain a <user_id, set_id, problem_id> triple"
 1897       unless defined $userProblemIDs[$i]
 1898              and ref $userProblemIDs[$i] eq "ARRAY"
 1899              and @{$userProblemIDs[$i]} == 3
 1900              and defined $userProblemIDs[$i]->[0]
 1901              and defined $userProblemIDs[$i]->[1]
 1902              and defined $userProblemIDs[$i]->[2];
 1903   }
 1904 
 1905   $WeBWorK::timer->continue("DB: getUserProblems start") if defined($WeBWorK::timer);
 1906   my @UserProblems = $self->getUserProblems(@userProblemIDs); # checked
 1907 
 1908   $WeBWorK::timer->continue("DB: pull out set/problem IDs start") if defined($WeBWorK::timer);
 1909   my @globalProblemIDs = map { [ $_->[1], $_->[2] ] } @userProblemIDs;
 1910   $WeBWorK::timer->continue("DB: getGlobalProblems start") if defined($WeBWorK::timer);
 1911   my @GlobalProblems = $self->getGlobalProblems(@globalProblemIDs); # checked
 1912 
 1913   $WeBWorK::timer->continue("DB: calc common fields start") if defined($WeBWorK::timer);
 1914   my %globalProblemFields = map { $_ => 1 } $self->newGlobalProblem->FIELDS;
 1915   my @commonFields = grep { exists $globalProblemFields{$_} } $self->newUserProblem->FIELDS;
 1916 
 1917   $WeBWorK::timer->continue("DB: merge start") if defined($WeBWorK::timer);
 1918   for (my $i = 0; $i < @UserProblems; $i++) {
 1919     my $UserProblem = $UserProblems[$i];
 1920     my $GlobalProblem = $GlobalProblems[$i];
 1921     next unless defined $UserProblem and defined $GlobalProblem;
 1922     foreach my $field (@commonFields) {
 1923       # FIXME: WW1Hash upgrades undefined fileds to "" when creating record objects
 1924       # Shouldn't we be testing for emptiness rather than definedness?
 1925       # I think the spec says that if a field is EMPTY the global value is used.
 1926       #next if defined $UserProblem->$field;
 1927       # ok, now we're testing for emptiness as well as definedness.
 1928       next if defined $UserProblem->$field and $UserProblem->$field ne "";
 1929       $UserProblem->$field($GlobalProblem->$field);
 1930     }
 1931   }
 1932   $WeBWorK::timer->continue("DB: merge done!") if defined($WeBWorK::timer);
 1933 
 1934   return @UserProblems;
 1935 }
 1936 
 1937 =back
 1938 
 1939 =cut
 1940 
 1941 ################################################################################
 1942 # debugging
 1943 ################################################################################
 1944 
 1945 #sub dumpDB($$) {
 1946 # my ($self, $table) = @_;
 1947 # return $self->{$table}->dumpDB();
 1948 #}
 1949 
 1950 ################################################################################
 1951 # utilities
 1952 ################################################################################
 1953 
 1954 sub checkKeyfields($) {
 1955   my ($Record) = @_;
 1956   foreach my $keyfield ($Record->KEYFIELDS) {
 1957     my $value = $Record->$keyfield;
 1958     croak "checkKeyfields: $keyfield is empty"
 1959       unless defined $value and $value ne "";
 1960 
 1961     if ($keyfield eq "problem_id") {
 1962       croak "checkKeyfields: invalid characters in $keyfield field: $value (valid characters are [0-9])"
 1963         unless $value =~ m/^\d*$/;
 1964     } else {
 1965       croak "checkKeyfields: invalid characters in $keyfield field: $value (valid characters are [A-Za-z0-9_])"
 1966         unless $value =~ m/^[\w-]*$/;
 1967     }
 1968   }
 1969 }
 1970 
 1971 =head1 AUTHOR
 1972 
 1973 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
 1974 
 1975 =cut
 1976 
 1977 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9