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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1668 - (view) (download) (as text)
Original Path: trunk/webwork2/lib/WeBWorK/DB.pm

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9