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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3019 - (view) (download) (as text)

1 : sh002i 775 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : sh002i 3019 # $CVSHeader: webwork2/lib/WeBWorK/DB.pm,v 1.58 2004/10/22 23:06:44 sh002i Exp $
5 : sh002i 1663 #
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 : sh002i 775 ################################################################################
16 :    
17 :     package WeBWorK::DB;
18 :    
19 :     =head1 NAME
20 :    
21 : sh002i 956 WeBWorK::DB - interface with the WeBWorK databases.
22 : sh002i 775
23 : sh002i 1012 =head1 SYNOPSIS
24 :    
25 : sh002i 1696 my $db = WeBWorK::DB->new($dbLayout);
26 : sh002i 1012
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 : sh002i 956 =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 : sh002i 1696 class, data source, and additional parameters are specified by the hash
48 :     referenced by C<$dbLayout>, usually taken from the course environment.
49 : sh002i 956
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 : sh002i 1568 / new* list* exists* add* get* get*s put* delete* \ <- api
61 : sh002i 956 +------------------------------------------------------------------+
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 : sh002i 1012 in a single hash in the WW1Hash schema, and the encoding scheme used.
72 : sh002i 956
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 : sh002i 1012 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 : sh002i 956
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 : sh002i 1012 =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 : sh002i 775 =cut
138 :    
139 :     use strict;
140 :     use warnings;
141 : sh002i 1096 use Carp;
142 : sh002i 904 use Data::Dumper;
143 : sh002i 1226 use WeBWorK::Timing;
144 : sh002i 775 use WeBWorK::Utils qw(runtime_use);
145 :    
146 :     ################################################################################
147 :     # constructor
148 :     ################################################################################
149 :    
150 : sh002i 956 =head1 CONSTRUCTOR
151 : sh002i 1012
152 : sh002i 956 =over
153 :    
154 : sh002i 2821 =item new($dbLayout)
155 : sh002i 956
156 : sh002i 2821 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 : sh002i 1012
159 :     =back
160 :    
161 : sh002i 1696 =head2 C<$dbLayout> Format
162 : sh002i 1108
163 : sh002i 1696 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 : sh002i 1108
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 : sh002i 1696 For each table defined in C<$dbLayout>, C<new> loads the record, schema, and
194 : sh002i 1167 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 : sh002i 956 =cut
200 :    
201 : sh002i 775 sub new($$) {
202 : sh002i 1696 my ($invocant, $dbLayout) = @_;
203 : sh002i 775 my $class = ref($invocant) || $invocant;
204 : sh002i 798 my $self = {};
205 : sh002i 931 bless $self, $class; # bless this here so we can pass it to the schema
206 : sh002i 775
207 :     # load the modules required to handle each table, and create driver
208 : sh002i 1696 my %dbLayout = %$dbLayout;
209 : sh002i 1167 foreach my $table (keys %dbLayout) {
210 :     my $layout = $dbLayout{$table};
211 : sh002i 798 my $record = $layout->{record};
212 : sh002i 775 my $schema = $layout->{schema};
213 :     my $driver = $layout->{driver};
214 :     my $source = $layout->{source};
215 : sh002i 808 my $params = $layout->{params};
216 : sh002i 775
217 : sh002i 798 runtime_use($record);
218 : sh002i 1096
219 :     runtime_use($driver);
220 :     my $driverObject = eval { $driver->new($source, $params) };
221 : sh002i 1167 croak "error instantiating DB driver $driver for table $table: $@"
222 : sh002i 1096 if $@;
223 :    
224 : sh002i 798 runtime_use($schema);
225 : sh002i 1096 my $schemaObject = eval { $schema->new(
226 : sh002i 1681 $self, $driverObject, $table, $record, $params) };
227 : sh002i 1167 croak "error instantiating DB schema $schema for table $table: $@"
228 : sh002i 1096 if $@;
229 :    
230 :     $self->{$table} = $schemaObject;
231 : sh002i 775 }
232 :    
233 :     return $self;
234 :     }
235 :    
236 : sh002i 1012 =head1 METHODS
237 :    
238 :     =cut
239 :    
240 : sh002i 775 ################################################################################
241 : sh002i 2312 # 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 : sh002i 2348 # FIXME: this is way too slow!
317 :     #my @userSetIDs = $self->{set_user}->list(undef, undef);
318 : sh002i 2312
319 : sh002i 2348 # 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 : sh002i 2312
331 : sh002i 2348 $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 : sh002i 3019 # get PSVNs for global user (•N)
347 : sh002i 2864 # this reads from "login<>global_user"
348 : sh002i 2348 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 : sh002i 3019 # get PSVNs for each setID (•N*M)
362 : sh002i 2864 # this reads from "set<>$_"
363 : sh002i 2348 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 : sh002i 2864 # uses: grep { m/^\d+$/ } keys %{ $self->{driver}->hash() }
368 : sh002i 2348 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 : sh002i 2864 #warn "the orphan PSVNs are: ", join(", ", keys %allPSVNs), "\n";
380 :    
381 : sh002i 2348 # 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 : sh002i 2312 }
407 :    
408 : sh002i 2348 $WeBWorK::timer->continue(__PACKAGE__ . "::hashDatabaseOK: done getting orphaned UserSets") if defined $WeBWorK::timer;
409 :    
410 :     if (keys %orphanUserSets) {
411 : sh002i 2864 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 : sh002i 2348 }
448 : sh002i 2312 }
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 : sh002i 775 # password functions
466 :     ################################################################################
467 :    
468 : sh002i 1012 =head2 Password Methods
469 :    
470 :     =over
471 :    
472 : sh002i 1201 =item newPassword()
473 :    
474 :     Returns a new, empty password object.
475 :    
476 :     =cut
477 :    
478 :     sub newPassword {
479 : sh002i 1635 my ($self, @prototype) = @_;
480 :     return $self->{password}->{record}->new(@prototype);
481 : sh002i 1201 }
482 :    
483 : sh002i 1012 =item listPasswords()
484 :    
485 :     Returns a list of user IDs representing the records in the password table.
486 :    
487 :     =cut
488 :    
489 : sh002i 1096 sub listPasswords {
490 : sh002i 775 my ($self) = @_;
491 : sh002i 1096
492 :     croak "listPasswords: requires 0 arguments"
493 :     unless @_ == 1;
494 :    
495 : sh002i 808 return map { $_->[0] }
496 :     $self->{password}->list(undef);
497 : sh002i 775 }
498 :    
499 : sh002i 1012 =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 : sh002i 1512 sub addPassword {
509 : sh002i 775 my ($self, $Password) = @_;
510 : sh002i 1096
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 : sh002i 1635
516 :     checkKeyfields($Password);
517 :    
518 : sh002i 1096 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 : sh002i 775 unless $self->{user}->exists($Password->user_id);
522 : sh002i 1096
523 : sh002i 775 return $self->{password}->add($Password);
524 :     }
525 :    
526 : sh002i 1012 =item getPassword($userID)
527 :    
528 :     If a record with a matching user ID exists, a record object containting that
529 : sh002i 1635 record's data will be returned. If no such record exists, one will be created.
530 : sh002i 1012
531 :     =cut
532 :    
533 : sh002i 1512 sub getPassword {
534 : sh002i 775 my ($self, $userID) = @_;
535 : sh002i 1096
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 : sh002i 1635 #return $self->{password}->get($userID);
542 :     return ( $self->getPasswords($userID) )[0];
543 : sh002i 775 }
544 :    
545 : sh002i 1512 =item getPasswords(@uesrIDs)
546 :    
547 :     Return a list of password records associated with the user IDs given. If there
548 : sh002i 1635 is no record associated with a given user ID, one will be created.
549 : sh002i 1512
550 :     =cut
551 :    
552 :     sub getPasswords {
553 :     my ($self, @userIDs) = @_;
554 :    
555 : sh002i 1641 #croak "getPasswords: requires 1 or more argument"
556 :     # unless @_ >= 2;
557 : sh002i 1512 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 : sh002i 1635 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 : sh002i 1512 }
580 :    
581 : sh002i 1012 =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 : sh002i 2747 $Password. If a matching password record does not exist, one will be created.
586 :     (This is different from most other "put" methods.)
587 : sh002i 1012
588 :     =cut
589 :    
590 : sh002i 775 sub putPassword($$) {
591 :     my ($self, $Password) = @_;
592 : sh002i 1096
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 : sh002i 1635
598 :     checkKeyfields($Password);
599 :    
600 : sh002i 2747 # 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 : sh002i 775 }
608 :    
609 : sh002i 1012 =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 : sh002i 775 sub deletePassword($$) {
618 :     my ($self, $userID) = @_;
619 : sh002i 1096
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 : sh002i 775 return $self->{password}->delete($userID);
626 :     }
627 :    
628 : sh002i 1012 =back
629 :    
630 :     =cut
631 :    
632 : sh002i 775 ################################################################################
633 :     # permission functions
634 :     ################################################################################
635 :    
636 : sh002i 1108 =head2 Permission Level Methods
637 :    
638 :     =over
639 :    
640 : sh002i 1201 =item newPermissionLevel()
641 :    
642 :     Returns a new, empty permission level object.
643 :    
644 :     =cut
645 :    
646 :     sub newPermissionLevel {
647 : sh002i 1635 my ($self, @prototype) = @_;
648 :     return $self->{permission}->{record}->new(@prototype);
649 : sh002i 1201 }
650 :    
651 : sh002i 1108 =item listPermissionLevels()
652 :    
653 :     Returns a list of user IDs representing the records in the permission table.
654 :    
655 :     =cut
656 :    
657 : sh002i 775 sub listPermissionLevels($) {
658 :     my ($self) = @_;
659 : sh002i 1096
660 :     croak "listPermissionLevels: requires 0 arguments"
661 :     unless @_ == 1;
662 :    
663 : sh002i 808 return map { $_->[0] }
664 :     $self->{permission}->list(undef);
665 : sh002i 775 }
666 :    
667 : sh002i 1108 =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 : sh002i 808 sub addPermissionLevel($$) {
677 : sh002i 775 my ($self, $PermissionLevel) = @_;
678 : sh002i 1096
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 : sh002i 1635
684 :     checkKeyfields($PermissionLevel);
685 :    
686 : sh002i 1096 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 : sh002i 775 unless $self->{user}->exists($PermissionLevel->user_id);
690 : sh002i 1096
691 : sh002i 775 return $self->{permission}->add($PermissionLevel);
692 :     }
693 :    
694 : sh002i 1108 =item getPermissionLevel($userID)
695 :    
696 :     If a record with a matching user ID exists, a record object containting that
697 : sh002i 1635 record's data will be returned. If no such record exists, one will be created.
698 : sh002i 1108
699 :     =cut
700 :    
701 : sh002i 775 sub getPermissionLevel($$) {
702 :     my ($self, $userID) = @_;
703 : sh002i 1096
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 : sh002i 1635 #return $self->{permission}->get($userID);
710 :     return ( $self->getPermissionLevels($userID) )[0];
711 : sh002i 775 }
712 :    
713 : sh002i 1512 =item getPermissionLevels(@uesrIDs)
714 :    
715 :     Return a list of permission level records associated with the user IDs given. If
716 : sh002i 1635 there is no record associated with a given user ID, one will be created.
717 : sh002i 1512
718 :     =cut
719 :    
720 :     sub getPermissionLevels {
721 :     my ($self, @userIDs) = @_;
722 :    
723 : sh002i 1641 #croak "getPermissionLevels: requires 1 or more argument"
724 :     # unless @_ >= 2;
725 : sh002i 1512 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 : sh002i 1635 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 : sh002i 1976 $PermissionLevels[$i] = $PermissionLevel;
743 : sh002i 1635 }
744 :     }
745 :     }
746 :    
747 :     return @PermissionLevels;
748 : sh002i 1512 }
749 :    
750 : sh002i 1108 =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 : sh002i 2747 exist, one will be created. (This is different from most other "put" methods.)
756 : sh002i 1108
757 :     =cut
758 :    
759 : sh002i 775 sub putPermissionLevel($$) {
760 :     my ($self, $PermissionLevel) = @_;
761 : sh002i 1096
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 : sh002i 1635
767 :     checkKeyfields($PermissionLevel);
768 :    
769 : sh002i 2747 # 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 : sh002i 775 }
777 :    
778 : sh002i 1108 =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 : sh002i 775 sub deletePermissionLevel($$) {
787 :     my ($self, $userID) = @_;
788 : sh002i 1096
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 : sh002i 775 return $self->{permission}->delete($userID);
795 :     }
796 :    
797 : sh002i 2955 =back
798 :    
799 :     =cut
800 :    
801 : sh002i 775 ################################################################################
802 :     # key functions
803 :     ################################################################################
804 :    
805 : sh002i 1108 =head2 Key Methods
806 :    
807 :     =over
808 :    
809 : sh002i 1201 =item newKey()
810 :    
811 :     Returns a new, empty key object.
812 :    
813 :     =cut
814 :    
815 :     sub newKey {
816 : sh002i 1635 my ($self, @prototype) = @_;
817 :     return $self->{key}->{record}->new(@prototype);
818 : sh002i 1201 }
819 :    
820 : sh002i 1108 =item listKeys()
821 :    
822 :     Returns a list of user IDs representing the records in the key table.
823 :    
824 :     =cut
825 :    
826 : sh002i 775 sub listKeys($) {
827 :     my ($self) = @_;
828 : sh002i 1096
829 :     croak "listKeys: requires 0 arguments"
830 :     unless @_ == 1;
831 :    
832 : sh002i 808 return map { $_->[0] }
833 :     $self->{key}->list(undef);
834 : sh002i 775 }
835 :    
836 : sh002i 1108 =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 : sh002i 808 sub addKey($$) {
846 : sh002i 775 my ($self, $Key) = @_;
847 : sh002i 1096
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 : sh002i 1635
853 :     checkKeyfields($Key);
854 :    
855 : sh002i 1096 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 : sh002i 775 unless $self->{user}->exists($Key->user_id);
859 : sh002i 1096
860 : sh002i 775 return $self->{key}->add($Key);
861 :     }
862 :    
863 : sh002i 1108 =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 : sh002i 775 sub getKey($$) {
872 :     my ($self, $userID) = @_;
873 : sh002i 1096
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 : sh002i 775 return $self->{key}->get($userID);
880 :     }
881 :    
882 : sh002i 1512 =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 : sh002i 1641 #croak "getKeys: requires 1 or more argument"
894 :     # unless @_ >= 2;
895 : sh002i 1512 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 : sh002i 1587 return $self->{key}->gets(map { [$_] } @userIDs);
901 : sh002i 1512 }
902 :    
903 : sh002i 1108 =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 : sh002i 775 sub putKey($$) {
912 :     my ($self, $Key) = @_;
913 : sh002i 1096
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 : sh002i 1635
919 :     checkKeyfields($Key);
920 :    
921 : sh002i 1096 croak "putKey: key not found (perhaps you meant to use addKey?)"
922 :     unless $self->{key}->exists($Key->user_id);
923 :    
924 : sh002i 775 return $self->{key}->put($Key);
925 :     }
926 :    
927 : sh002i 1108 =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 : sh002i 775 sub deleteKey($$) {
936 :     my ($self, $userID) = @_;
937 : sh002i 1096
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 : sh002i 775 return $self->{key}->delete($userID);
944 :     }
945 :    
946 : sh002i 2955 =back
947 :    
948 :     =cut
949 :    
950 : sh002i 775 ################################################################################
951 :     # user functions
952 :     ################################################################################
953 :    
954 : sh002i 1108 =head2 User Methods
955 :    
956 :     =over
957 :    
958 : sh002i 1201 =item newUser()
959 :    
960 :     Returns a new, empty user object.
961 :    
962 :     =cut
963 :    
964 :     sub newUser {
965 : sh002i 1635 my ($self, @prototype) = @_;
966 :     return $self->{user}->{record}->new(@prototype);
967 : sh002i 1201 }
968 :    
969 : sh002i 1108 =item listUsers()
970 :    
971 :     Returns a list of user IDs representing the records in the user table.
972 :    
973 :     =cut
974 :    
975 : sh002i 1568 sub listUsers {
976 : sh002i 775 my ($self) = @_;
977 : sh002i 1096
978 :     croak "listUsers: requires 0 arguments"
979 :     unless @_ == 1;
980 :    
981 : sh002i 808 return map { $_->[0] }
982 :     $self->{user}->list(undef);
983 : sh002i 775 }
984 :    
985 : sh002i 1108 =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 : sh002i 1568 sub addUser {
994 : sh002i 775 my ($self, $User) = @_;
995 : sh002i 1096
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 : sh002i 1635
1001 :     checkKeyfields($User);
1002 :    
1003 : sh002i 1096 croak "addUser: user exists (perhaps you meant to use putUser?)"
1004 :     if $self->{user}->exists($User->user_id);
1005 :    
1006 : sh002i 775 return $self->{user}->add($User);
1007 :     }
1008 :    
1009 : sh002i 1108 =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 : sh002i 1568 sub getUser {
1018 : sh002i 775 my ($self, $userID) = @_;
1019 : sh002i 1096
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 : sh002i 775 return $self->{user}->get($userID);
1026 :     }
1027 :    
1028 : sh002i 1512 =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 : sh002i 1641 #croak "getUsers: requires 1 or more argument"
1040 :     # unless @_ >= 2;
1041 : sh002i 1512 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 : sh002i 1587 return $self->{user}->gets(map { [$_] } @userIDs);
1047 : sh002i 1512 }
1048 :    
1049 : sh002i 1108 =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 : sh002i 1568 sub putUser {
1058 : sh002i 775 my ($self, $User) = @_;
1059 : sh002i 1096
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 : sh002i 1635
1065 :     checkKeyfields($User);
1066 :    
1067 : sh002i 1096 croak "putUser: user not found (perhaps you meant to use addUser?)"
1068 :     unless $self->{user}->exists($User->user_id);
1069 :    
1070 : sh002i 775 return $self->{user}->put($User);
1071 :     }
1072 :    
1073 : sh002i 1108 =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 : sh002i 1568 sub deleteUser {
1084 : sh002i 775 my ($self, $userID) = @_;
1085 : sh002i 1096
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 : sh002i 1167 $self->deleteUserSet($userID, undef);
1092 : sh002i 775 $self->deletePassword($userID);
1093 :     $self->deletePermissionLevel($userID);
1094 :     $self->deleteKey($userID);
1095 :     return $self->{user}->delete($userID);
1096 :     }
1097 :    
1098 : sh002i 1583 =back
1099 :    
1100 :     =cut
1101 :    
1102 : sh002i 775 ################################################################################
1103 :     # set functions
1104 :     ################################################################################
1105 :    
1106 : sh002i 1583 =head2 Global Set Methods
1107 :    
1108 :     FIXME: write this
1109 :    
1110 :     =over
1111 :    
1112 :     =cut
1113 :    
1114 : sh002i 1696 =item newGlobalSet()
1115 :    
1116 :     =cut
1117 :    
1118 : sh002i 1201 sub newGlobalSet {
1119 : sh002i 1635 my ($self, @prototype) = @_;
1120 :     return $self->{set}->{record}->new(@prototype);
1121 : sh002i 1201 }
1122 :    
1123 : sh002i 1696 =item listGlobalSets()
1124 :    
1125 :     =cut
1126 :    
1127 : sh002i 1641 sub listGlobalSets {
1128 : sh002i 775 my ($self) = @_;
1129 : sh002i 1096
1130 :     croak "listGlobalSets: requires 0 arguments"
1131 :     unless @_ == 1;
1132 :    
1133 : sh002i 808 return map { $_->[0] }
1134 :     $self->{set}->list(undef);
1135 : sh002i 775 }
1136 :    
1137 : sh002i 1696 =item addGlobalSet($GlobalSet)
1138 :    
1139 :     =cut
1140 :    
1141 : sh002i 1641 sub addGlobalSet {
1142 : sh002i 775 my ($self, $GlobalSet) = @_;
1143 : sh002i 1096
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 : sh002i 1635
1149 :     checkKeyfields($GlobalSet);
1150 :    
1151 : sh002i 1096 croak "addGlobalSet: global set exists (perhaps you meant to use putGlobalSet?)"
1152 :     if $self->{set}->exists($GlobalSet->set_id);
1153 :    
1154 : sh002i 775 return $self->{set}->add($GlobalSet);
1155 :     }
1156 :    
1157 : sh002i 1696 =item addGlobalSet($setID)
1158 :    
1159 :     =cut
1160 :    
1161 : sh002i 1641 sub getGlobalSet {
1162 : sh002i 775 my ($self, $setID) = @_;
1163 : sh002i 1096
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 : sh002i 775 return $self->{set}->get($setID);
1170 :     }
1171 :    
1172 : sh002i 1512 =item getGlobalSets(@setIDs)
1173 :    
1174 : sh002i 1641 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 : sh002i 1512
1178 :     =cut
1179 :    
1180 :     sub getGlobalSets {
1181 :     my ($self, @setIDs) = @_;
1182 :    
1183 : sh002i 1641 #croak "getGlobalSets: requires 1 or more argument"
1184 :     # unless @_ >= 2;
1185 : sh002i 1512 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 : sh002i 1587 return $self->{set}->gets(map { [$_] } @setIDs);
1191 : sh002i 1512 }
1192 :    
1193 : sh002i 1696 =item addGlobalSet($GlobalSet)
1194 :    
1195 :     =cut
1196 :    
1197 : sh002i 1641 sub putGlobalSet {
1198 : sh002i 775 my ($self, $GlobalSet) = @_;
1199 : sh002i 1096
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 : sh002i 1635
1205 :     checkKeyfields($GlobalSet);
1206 :    
1207 : sh002i 1096 croak "putGlobalSet: global set not found (perhaps you meant to use addGlobalSet?)"
1208 :     unless $self->{set}->exists($GlobalSet->set_id);
1209 :    
1210 : sh002i 775 return $self->{set}->put($GlobalSet);
1211 :     }
1212 :    
1213 : sh002i 1696 =item addGlobalSet($setID)
1214 :    
1215 :     =cut
1216 :    
1217 : sh002i 1641 sub deleteGlobalSet {
1218 : sh002i 775 my ($self, $setID) = @_;
1219 : sh002i 1096
1220 :     croak "deleteGlobalSet: requires 1 argument"
1221 :     unless @_ == 2;
1222 :     croak "deleteGlobalSet: argument 1 must contain a set_id"
1223 : sh002i 1167 unless defined $setID or caller eq __PACKAGE__;
1224 : sh002i 1096
1225 : sh002i 1167 $self->deleteUserSet(undef, $setID);
1226 :     $self->deleteGlobalProblem($setID, undef);
1227 : sh002i 775 return $self->{set}->delete($setID);
1228 :     }
1229 :    
1230 : sh002i 1583 =back
1231 :    
1232 :     =cut
1233 :    
1234 : sh002i 775 ################################################################################
1235 :     # set_user functions
1236 :     ################################################################################
1237 :    
1238 : sh002i 1583 =head2 User-Specific Set Methods
1239 :    
1240 :     FIXME: write this
1241 :    
1242 :     =over
1243 :    
1244 :     =cut
1245 :    
1246 : sh002i 1201 sub newUserSet {
1247 : sh002i 1635 my ($self, @prototype) = @_;
1248 :     return $self->{set_user}->{record}->new(@prototype);
1249 : sh002i 1201 }
1250 :    
1251 : sh002i 1661 sub countSetUsers {
1252 :     my ($self, $setID) = @_;
1253 :    
1254 : toenail 2330 croak "countSetUsers: requires 1 argument"
1255 : sh002i 1661 unless @_ == 2;
1256 : toenail 2330 croak "countSetUsers: argument 1 must contain a set_id"
1257 : sh002i 1661 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 : sh002i 1641 sub listSetUsers {
1267 : sh002i 909 my ($self, $setID) = @_;
1268 : sh002i 1096
1269 : sh002i 1661 carp "listSetUsers called in SCALAR context: use countSetUsers instead!\n"
1270 :     unless wantarray;
1271 :    
1272 : sh002i 1096 croak "listSetUsers: requires 1 argument"
1273 :     unless @_ == 2;
1274 :     croak "listSetUsers: argument 1 must contain a set_id"
1275 :     unless defined $setID;
1276 :    
1277 : sh002i 909 return map { $_->[0] } # extract user_id
1278 :     $self->{set_user}->list(undef, $setID);
1279 :     }
1280 :    
1281 : toenail 2330 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 : sh002i 1641 sub listUserSets {
1293 : sh002i 775 my ($self, $userID) = @_;
1294 : sh002i 1096
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 : sh002i 808 return map { $_->[1] } # extract set_id
1301 :     $self->{set_user}->list($userID, undef);
1302 : sh002i 775 }
1303 :    
1304 : sh002i 1641 sub addUserSet {
1305 : sh002i 775 my ($self, $UserSet) = @_;
1306 : sh002i 1096
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 : sh002i 1635
1312 :     checkKeyfields($UserSet);
1313 :    
1314 : sh002i 1096 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 : sh002i 775 unless $self->{user}->exists($UserSet->user_id);
1318 : sh002i 1096 croak "addUserSet: set ", $UserSet->set_id, " not found"
1319 : sh002i 775 unless $self->{set}->exists($UserSet->set_id);
1320 : sh002i 1096
1321 : sh002i 775 return $self->{set_user}->add($UserSet);
1322 :     }
1323 :    
1324 : sh002i 1641 sub getUserSet {
1325 : sh002i 775 my ($self, $userID, $setID) = @_;
1326 : sh002i 1096
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 : sh002i 1589 #return $self->{set_user}->get($userID, $setID);
1335 :     return ( $self->getUserSets([$userID, $setID]) )[0];
1336 : sh002i 775 }
1337 :    
1338 : sh002i 1512 =item getUserSets(@userSetIDs)
1339 :    
1340 : sh002i 1641 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 : sh002i 1512 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 : sh002i 1641 #croak "getUserSets: requires 1 or more argument"
1351 :     # unless @_ >= 2;
1352 : sh002i 1512 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 : sh002i 1568 return $self->{set_user}->gets(@userSetIDs);
1362 : sh002i 1512 }
1363 :    
1364 : sh002i 1641 sub putUserSet {
1365 : sh002i 775 my ($self, $UserSet) = @_;
1366 : sh002i 1096
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 : sh002i 1635
1372 :     checkKeyfields($UserSet);
1373 :    
1374 : sh002i 1096 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 : sh002i 775 return $self->{set_user}->put($UserSet);
1382 :     }
1383 :    
1384 : sh002i 1641 sub deleteUserSet {
1385 : sh002i 775 my ($self, $userID, $setID) = @_;
1386 : sh002i 1096
1387 :     croak "getUserSet: requires 2 arguments"
1388 :     unless @_ == 3;
1389 :     croak "getUserSet: argument 1 must contain a user_id"
1390 : sh002i 1167 unless defined $userID or caller eq __PACKAGE__;
1391 : sh002i 1096 croak "getUserSet: argument 2 must contain a set_id"
1392 : sh002i 1167 unless defined $userID or caller eq __PACKAGE__;
1393 : sh002i 1096
1394 : sh002i 1167 $self->deleteUserProblem($userID, $setID, undef);
1395 : sh002i 775 return $self->{set_user}->delete($userID, $setID);
1396 :     }
1397 :    
1398 : sh002i 1583 =back
1399 :    
1400 :     =cut
1401 :    
1402 : sh002i 775 ################################################################################
1403 :     # problem functions
1404 :     ################################################################################
1405 :    
1406 : sh002i 1583 =head2 Global Problem Methods
1407 :    
1408 :     FIXME: write this
1409 :    
1410 :     =over
1411 :    
1412 :     =cut
1413 :    
1414 : sh002i 1201 sub newGlobalProblem {
1415 : sh002i 1635 my ($self, @prototype) = @_;
1416 :     return $self->{problem}->{record}->new(@prototype);
1417 : sh002i 1201 }
1418 :    
1419 : sh002i 1641 sub listGlobalProblems {
1420 : sh002i 775 my ($self, $setID) = @_;
1421 : sh002i 1096
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 : sh002i 775 return map { $_->[1] }
1428 : sh002i 1096 $self->{problem}->list($setID, undef);
1429 : sh002i 775 }
1430 :    
1431 : sh002i 1641 sub addGlobalProblem {
1432 : sh002i 775 my ($self, $GlobalProblem) = @_;
1433 : sh002i 1096
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 : sh002i 1635
1439 :     checkKeyfields($GlobalProblem);
1440 :    
1441 : sh002i 1096 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 : sh002i 775 unless $self->{set}->exists($GlobalProblem->set_id);
1445 : sh002i 1096
1446 : sh002i 775 return $self->{problem}->add($GlobalProblem);
1447 :     }
1448 :    
1449 : sh002i 1641 sub getGlobalProblem {
1450 : sh002i 775 my ($self, $setID, $problemID) = @_;
1451 : sh002i 1096
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 : sh002i 916 return $self->{problem}->get($setID, $problemID);
1460 : sh002i 775 }
1461 :    
1462 : sh002i 1512 =item getGlobalProblems(@problemIDs)
1463 :    
1464 : sh002i 1641 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 : sh002i 1512
1469 :     =cut
1470 :    
1471 :     sub getGlobalProblems {
1472 :     my ($self, @problemIDs) = @_;
1473 :    
1474 : sh002i 1641 #croak "getGlobalProblems: requires 1 or more argument"
1475 :     # unless @_ >= 2;
1476 : sh002i 1512 foreach my $i (0 .. $#problemIDs) {
1477 : sh002i 3019 croak "getGlobalProblems: element $i of argument list must contain a <set_id, problem_id> pair"
1478 : sh002i 1512 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 : sh002i 1568 return $self->{problem}->gets(@problemIDs);
1486 : sh002i 1512 }
1487 :    
1488 : sh002i 1672 =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 : sh002i 1641 sub putGlobalProblem {
1513 : sh002i 775 my ($self, $GlobalProblem) = @_;
1514 : sh002i 1096
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 : sh002i 1635
1520 :     checkKeyfields($GlobalProblem);
1521 :    
1522 : sh002i 1096 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 : sh002i 775 return $self->{problem}->put($GlobalProblem);
1528 :     }
1529 :    
1530 : sh002i 1641 sub deleteGlobalProblem {
1531 : sh002i 775 my ($self, $setID, $problemID) = @_;
1532 : sh002i 1096
1533 : sh002i 1167 croak "deleteGlobalProblem: requires 2 arguments"
1534 : sh002i 1096 unless @_ == 3;
1535 : sh002i 1167 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 : sh002i 1096
1540 : sh002i 1167 $self->deleteUserProblem(undef, $setID, $problemID);
1541 : sh002i 775 return $self->{problem}->delete($setID, $problemID);
1542 :     }
1543 :    
1544 : sh002i 1583 =back
1545 :    
1546 :     =cut
1547 :    
1548 : sh002i 775 ################################################################################
1549 :     # problem_user functions
1550 :     ################################################################################
1551 :    
1552 : sh002i 1583 =head2 User-Specific Problem Methods
1553 :    
1554 :     FIXME: write this
1555 :    
1556 :     =over
1557 :    
1558 :     =cut
1559 :    
1560 : sh002i 1201 sub newUserProblem {
1561 : sh002i 1635 my ($self, @prototype) = @_;
1562 :     return $self->{problem_user}->{record}->new(@prototype);
1563 : sh002i 1201 }
1564 :    
1565 : sh002i 1661 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 : sh002i 1641 sub listProblemUsers {
1583 : sh002i 923 my ($self, $setID, $problemID) = @_;
1584 : sh002i 1096
1585 : sh002i 1661 carp "listProblemUsers called in SCALAR context: use countProblemUsers instead!\n"
1586 :     unless wantarray;
1587 :    
1588 : sh002i 1096 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 : sh002i 923 return map { $_->[0] } # extract user_id
1596 :     $self->{problem_user}->list(undef, $setID, $problemID);
1597 :     }
1598 :    
1599 : sh002i 1641 sub listUserProblems {
1600 : sh002i 775 my ($self, $userID, $setID) = @_;
1601 : sh002i 1096
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 : sh002i 923 return map { $_->[2] } # extract problem_id
1610 : sh002i 808 $self->{problem_user}->list($userID, $setID, undef);
1611 : sh002i 775 }
1612 :    
1613 : sh002i 1641 sub addUserProblem {
1614 : sh002i 775 my ($self, $UserProblem) = @_;
1615 : sh002i 1096
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 : sh002i 1635
1621 :     checkKeyfields($UserProblem);
1622 :    
1623 : sh002i 1096 croak "addUserProblem: user problem exists (perhaps you meant to use putUserProblem?)"
1624 : malsyned 1185 if $self->{problem_user}->exists($UserProblem->user_id, $UserProblem->set_id, $UserProblem->problem_id);
1625 : sh002i 1096 croak "addUserProblem: user set ", $UserProblem->set_id, " for user ", $UserProblem->user_id, " not found"
1626 : sh002i 808 unless $self->{set_user}->exists($UserProblem->user_id, $UserProblem->set_id);
1627 : sh002i 1096 croak "addUserProblem: problem ", $UserProblem->problem_id, " in set ", $UserProblem->set_id, " not found"
1628 : sh002i 914 unless $self->{problem}->exists($UserProblem->set_id, $UserProblem->problem_id);
1629 : sh002i 1096
1630 : sh002i 775 return $self->{problem_user}->add($UserProblem);
1631 :     }
1632 :    
1633 : sh002i 1641 sub getUserProblem {
1634 : sh002i 775 my ($self, $userID, $setID, $problemID) = @_;
1635 : sh002i 1096
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 : sh002i 1589 return ( $self->getUserProblems([$userID, $setID, $problemID]) )[0];
1646 : sh002i 775 }
1647 :    
1648 : sh002i 1512 =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 : sh002i 1641 #croak "getUserProblems: requires 1 or more argument"
1662 :     # unless @_ >= 2;
1663 : sh002i 1512 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 : sh002i 1586 return $self->{problem_user}->gets(@userProblemIDs);
1674 : sh002i 1512 }
1675 :    
1676 : sh002i 1668 =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 : sh002i 1672 croak "getAllUserProblems: requires 2 arguments"
1688 : sh002i 1668 unless @_ == 3;
1689 : sh002i 1672 croak "getAllUserProblems: argument 1 must contain a user_id"
1690 : sh002i 1668 unless defined $userID;
1691 : sh002i 1672 croak "getAllUserProblems: argument 2 must contain a set_id"
1692 : sh002i 1668 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 : sh002i 1641 sub putUserProblem {
1703 : sh002i 775 my ($self, $UserProblem) = @_;
1704 : sh002i 1096
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 : sh002i 1635
1710 :     checkKeyfields($UserProblem);
1711 :    
1712 : sh002i 1096 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 : malsyned 1104 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 : sh002i 1096 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 : sh002i 775 return $self->{problem_user}->put($UserProblem);
1720 :     }
1721 :    
1722 : sh002i 1641 sub deleteUserProblem {
1723 : sh002i 775 my ($self, $userID, $setID, $problemID) = @_;
1724 : sh002i 1096
1725 :     croak "getUserProblem: requires 3 arguments"
1726 :     unless @_ == 4;
1727 :     croak "getUserProblem: argument 1 must contain a user_id"
1728 : sh002i 1167 unless defined $userID or caller eq __PACKAGE__;
1729 : sh002i 1096 croak "getUserProblem: argument 2 must contain a set_id"
1730 : sh002i 1167 unless defined $setID or caller eq __PACKAGE__;
1731 : sh002i 1096 croak "getUserProblem: argument 3 must contain a problem_id"
1732 : sh002i 1167 unless defined $problemID or caller eq __PACKAGE__;
1733 : sh002i 1096
1734 : sh002i 775 return $self->{problem_user}->delete($userID, $setID, $problemID);
1735 :     }
1736 :    
1737 : sh002i 1583 =back
1738 :    
1739 :     =cut
1740 :    
1741 : sh002i 775 ################################################################################
1742 :     # set+set_user functions
1743 :     ################################################################################
1744 :    
1745 : sh002i 1583 =head2 Set Merging Methods
1746 :    
1747 : sh002i 1641 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 : sh002i 1583
1751 :     =over
1752 :    
1753 :     =cut
1754 :    
1755 : sh002i 1096 sub getGlobalUserSet {
1756 :     carp "getGlobalUserSet: this method is deprecated -- use getMergedSet instead";
1757 :     return shift->getMergedSet(@_);
1758 :     }
1759 :    
1760 : sh002i 1641 =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 : sh002i 1096 sub getMergedSet {
1768 : sh002i 798 my ($self, $userID, $setID) = @_;
1769 : sh002i 1096
1770 : gage 1541 croak "getMergedSet: requires 2 arguments"
1771 : sh002i 1096 unless @_ == 3;
1772 : gage 1541 croak "getMergedSet: argument 1 must contain a user_id"
1773 : sh002i 1096 unless defined $userID;
1774 : gage 1541 croak "getMergedSet: argument 2 must contain a set_id"
1775 : sh002i 1096 unless defined $setID;
1776 :    
1777 : sh002i 1586 return ( $self->getMergedSets([$userID, $setID]) )[0];
1778 : sh002i 798 }
1779 : sh002i 775
1780 : sh002i 1641 =item getMegedSets(@userSetIDs)
1781 : sh002i 1512
1782 : sh002i 1641 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 : sh002i 1512
1787 :     =cut
1788 :    
1789 : gage 1541 sub getMergedSets {
1790 : sh002i 1512 my ($self, @userSetIDs) = @_;
1791 :    
1792 : sh002i 1641 #croak "getMergedSets: requires 1 or more argument"
1793 :     # unless @_ >= 2;
1794 : sh002i 1512 foreach my $i (0 .. $#userSetIDs) {
1795 : gage 1541 croak "getMergedSets: element $i of argument list must contain a <user_id, set_id> pair"
1796 : sh002i 1512 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 : sh002i 1649 # a horrible, terrible hack ;)
1804 :     if (ref $self->{set_user} eq "WeBWorK::DB::Schema::WW1Hash"
1805 : sh002i 1657 and ref $self->{set} eq "WeBWorK::DB::Schema::GlobalTableEmulator") {
1806 :     #warn __PACKAGE__.": using a terrible hack.\n";
1807 : sh002i 1649 $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 : sh002i 1635 my @UserSets = $self->getUserSets(@userSetIDs); # checked
1815 : sh002i 1583
1816 : sh002i 1649 $WeBWorK::timer->continue("DB: pull out set IDs start") if defined($WeBWorK::timer);
1817 : sh002i 1587 my @globalSetIDs = map { $_->[1] } @userSetIDs;
1818 : sh002i 1649 $WeBWorK::timer->continue("DB: getGlobalSets start") if defined($WeBWorK::timer);
1819 : sh002i 1635 my @GlobalSets = $self->getGlobalSets(@globalSetIDs); # checked
1820 : sh002i 1583
1821 : sh002i 1649 $WeBWorK::timer->continue("DB: calc common fields start") if defined($WeBWorK::timer);
1822 : sh002i 1583 my %globalSetFields = map { $_ => 1 } $self->newGlobalSet->FIELDS;
1823 :     my @commonFields = grep { exists $globalSetFields{$_} } $self->newUserSet->FIELDS;
1824 :    
1825 : sh002i 1649 $WeBWorK::timer->continue("DB: merge start") if defined($WeBWorK::timer);
1826 : sh002i 1583 for (my $i = 0; $i < @UserSets; $i++) {
1827 :     my $UserSet = $UserSets[$i];
1828 :     my $GlobalSet = $GlobalSets[$i];
1829 : sh002i 1635 next unless defined $UserSet and defined $GlobalSet;
1830 : sh002i 1583 foreach my $field (@commonFields) {
1831 : sh002i 2319 #next if defined $UserSet->$field;
1832 : sh002i 2328 # ok, now we're testing for emptiness as well as definedness.
1833 :     next if defined $UserSet->$field and $UserSet->$field ne "";
1834 : sh002i 1583 $UserSet->$field($GlobalSet->$field);
1835 :     }
1836 :     }
1837 : sh002i 1649 $WeBWorK::timer->continue("DB: merge done!") if defined($WeBWorK::timer);
1838 : sh002i 1583
1839 :     return @UserSets;
1840 : sh002i 1512 }
1841 :    
1842 : sh002i 1583 =back
1843 : gage 1541
1844 : sh002i 1583 =cut
1845 : gage 1541
1846 : sh002i 775 ################################################################################
1847 :     # problem+problem_user functions
1848 :     ################################################################################
1849 :    
1850 : sh002i 1583 =head2 Problem Merging Methods
1851 :    
1852 : sh002i 1641 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 : sh002i 1583
1857 :     =over
1858 :    
1859 :     =cut
1860 :    
1861 : sh002i 1096 sub getGlobalUserProblem {
1862 :     carp "getGlobalUserProblem: this method is deprecated -- use getMergedProblem instead";
1863 :     return shift->getMergedProblem(@_);
1864 :     }
1865 :    
1866 : sh002i 1641 =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 : sh002i 1096 sub getMergedProblem {
1874 : sh002i 798 my ($self, $userID, $setID, $problemID) = @_;
1875 : sh002i 1096
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 : sh002i 1586 return ( $self->getMergedProblems([$userID, $setID, $problemID]) )[0];
1886 : sh002i 798 }
1887 : sh002i 775
1888 : sh002i 1512 =item getMergedProblems(@userProblemIDs)
1889 :    
1890 : sh002i 1641 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 : sh002i 1512
1896 :     =cut
1897 :    
1898 :     sub getMergedProblems {
1899 :     my ($self, @userProblemIDs) = @_;
1900 :    
1901 : sh002i 1641 #croak "getMergedProblems: requires 1 or more argument"
1902 :     # unless @_ >= 2;
1903 : sh002i 1512 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 : sh002i 1649 $WeBWorK::timer->continue("DB: getUserProblems start") if defined($WeBWorK::timer);
1914 : sh002i 1635 my @UserProblems = $self->getUserProblems(@userProblemIDs); # checked
1915 : sh002i 1583
1916 : sh002i 1649 $WeBWorK::timer->continue("DB: pull out set/problem IDs start") if defined($WeBWorK::timer);
1917 : sh002i 1583 my @globalProblemIDs = map { [ $_->[1], $_->[2] ] } @userProblemIDs;
1918 : sh002i 1649 $WeBWorK::timer->continue("DB: getGlobalProblems start") if defined($WeBWorK::timer);
1919 : sh002i 1635 my @GlobalProblems = $self->getGlobalProblems(@globalProblemIDs); # checked
1920 : sh002i 1583
1921 : sh002i 1649 $WeBWorK::timer->continue("DB: calc common fields start") if defined($WeBWorK::timer);
1922 : sh002i 1583 my %globalProblemFields = map { $_ => 1 } $self->newGlobalProblem->FIELDS;
1923 :     my @commonFields = grep { exists $globalProblemFields{$_} } $self->newUserProblem->FIELDS;
1924 :    
1925 : sh002i 1649 $WeBWorK::timer->continue("DB: merge start") if defined($WeBWorK::timer);
1926 : sh002i 1583 for (my $i = 0; $i < @UserProblems; $i++) {
1927 :     my $UserProblem = $UserProblems[$i];
1928 :     my $GlobalProblem = $GlobalProblems[$i];
1929 : sh002i 1635 next unless defined $UserProblem and defined $GlobalProblem;
1930 : sh002i 1583 foreach my $field (@commonFields) {
1931 : sh002i 2105 # 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 : sh002i 2319 #next if defined $UserProblem->$field;
1935 : sh002i 2328 # ok, now we're testing for emptiness as well as definedness.
1936 :     next if defined $UserProblem->$field and $UserProblem->$field ne "";
1937 : sh002i 1583 $UserProblem->$field($GlobalProblem->$field);
1938 :     }
1939 :     }
1940 : sh002i 1649 $WeBWorK::timer->continue("DB: merge done!") if defined($WeBWorK::timer);
1941 : sh002i 1583
1942 :     return @UserProblems;
1943 : sh002i 1512 }
1944 :    
1945 : sh002i 1583 =back
1946 :    
1947 :     =cut
1948 :    
1949 : sh002i 808 ################################################################################
1950 :     # debugging
1951 :     ################################################################################
1952 :    
1953 : sh002i 1583 #sub dumpDB($$) {
1954 :     # my ($self, $table) = @_;
1955 :     # return $self->{$table}->dumpDB();
1956 :     #}
1957 : sh002i 808
1958 : sh002i 1199 ################################################################################
1959 : sh002i 1635 # utilities
1960 : sh002i 1199 ################################################################################
1961 :    
1962 :     sub checkKeyfields($) {
1963 :     my ($Record) = @_;
1964 :     foreach my $keyfield ($Record->KEYFIELDS) {
1965 : sh002i 1226 my $value = $Record->$keyfield;
1966 :     croak "checkKeyfields: $keyfield is empty"
1967 :     unless defined $value and $value ne "";
1968 : sh002i 1635
1969 : sh002i 1226 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 : malsyned 1299 unless $value =~ m/^[\w-]*$/;
1975 : sh002i 1226 }
1976 : sh002i 1199 }
1977 :     }
1978 :    
1979 : sh002i 1012 =head1 AUTHOR
1980 :    
1981 :     Written by Sam Hathaway, sh002i (at) math.rochester.edu.
1982 :    
1983 : sh002i 1035 =cut
1984 : gage 1023
1985 : sh002i 775 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9