################################################################################ # WeBWorK Online Homework Delivery System> # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader: webwork2/lib/WeBWorK/DB.pm,v 1.111 2010/05/19 01:44:05 gage Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ package WeBWorK::DB; =head1 NAME WeBWorK::DB - interface with the WeBWorK databases. =head1 SYNOPSIS my $db = WeBWorK::DB->new($dbLayout); my @userIDs = $db->listUsers(); my $Sam = $db->{user}->{record}->new(); $Sam->user_id("sammy"); $Sam->first_name("Sam"); $Sam->last_name("Hathaway"); # etc. $db->addUser($User); my $Dennis = $db->getUser("dennis"); $Dennis->status("C"); $db->putUser->($Dennis); $db->deleteUser("sammy"); =head1 DESCRIPTION WeBWorK::DB provides a consistent interface to a number of database backends. Access and modification functions are provided for each logical table used by the webwork system. The particular backend ("schema" and "driver"), record class, data source, and additional parameters are specified by the hash referenced by C<$dbLayout>, usually taken from the course environment. =head1 ARCHITECTURE The new database system uses a three-tier architecture to insulate each layer from the adjacent layers. =head2 Top Layer: DB The top layer of the architecture is the DB module. It provides the methods listed below, and uses schema modules (via tables) to implement those methods. / new* list* exists* add* get* get*s put* delete* \ <- api +------------------------------------------------------------------+ | DB | +------------------------------------------------------------------+ \ password permission key user set set_user problem problem_user / <- tables =head2 Middle Layer: Schemas The middle layer of the architecture is provided by one or more schema modules. They are called "schema" modules because they control the structure of the data for a table. The schema modules provide an API that matches the requirements of the DB layer, on a per-table basis. Each schema module has a style that determines which drivers it can interface with. For example, SQL is an "dbi" style schema. =head2 Bottom Layer: Drivers Driver modules implement a style for a schema. They provide physical access to a data source containing the data for a table. The style of a driver determines what methods it provides. All drivers provide C and C methods. A dbi style driver provides a C method which returns the DBI handle. =head2 Record Types In C<%dblayout>, each table is assigned a record class, used for passing complete records to and from the database. The default record classes are subclasses of the WeBWorK::DB::Record class, and are named as follows: User, Password, PermissionLevel, Key, Set, UserSet, Problem, UserProblem. In the following documentation, a reference the the record class for a table means the record class currently defined for that table in C<%dbLayout>. =cut use strict; use warnings; use Carp; use Data::Dumper; use Scalar::Util qw/blessed/; use WeBWorK::DB::Schema; use WeBWorK::DB::Utils qw/make_vsetID grok_vsetID grok_setID_from_vsetID_sql grok_versionID_from_vsetID_sql/; use WeBWorK::Debug; use WeBWorK::Utils qw(runtime_use); =for comment These exceptions will replace the ones in WeBWorK::DB::Schema and will be allowed to propagate out to calling code. The following callers will have to be changed to catch these exceptions instead of doing string matching: lib/WebworkSOAP.pm: if ($@ =~ m/user set exists/) { lib/WeBWorK/ContentGenerator/Instructor.pm: if ($@ =~ m/user set exists/) { lib/WeBWorK/ContentGenerator/Instructor.pm: if ( $@ =~ m/user set exists/ ) { lib/WeBWorK/ContentGenerator/Instructor.pm: if ($@ =~ m/user problem exists/) { lib/WeBWorK/ContentGenerator/Instructor.pm: if ($@ =~ m/user problem exists/) { lib/WeBWorK/ContentGenerator/Instructor.pm: next if $@ =~ m/user set exists/; lib/WeBWorK/Utils/DBImportExport.pm: if ($@ =~ m/exists/) { lib/WeBWorK/DB.pm: if ($@ and $@ !~ m/password exists/) { lib/WeBWorK/DB.pm: if ($@ and $@ !~ m/permission level exists/) { How these exceptions should be used: * RecordExists is thrown by the DBI error handler (handle_error in Schema::NewSQL::Std) when in INSERT fails because a record exists. Thus it can be thrown via addUser, addPassword, etc. * RecordNotFound should be thrown when we try to UPDATE and zero rows were affected. Problem: Frank Wolfs (UofR PAS) may have a MySQL server that returns 0 when updating even when a record was modified. What's up with that? There's some question as to where we should throw this: in this file's put* methods? In Std.pm's put method? Or in update_fields and update_fields_i? * DependencyNotFound should be throws when we check for a record that is needed to insert another record (e.g. password depends on user). These checks are done in this file, so we'll throw this exception from there. =cut use Exception::Class ( 'WeBWorK::DB::Ex' => {}, 'WeBWorK::DB::Ex::RecordExists' => { isa => 'WeBWorK::DB::Ex', fields => ['type', 'key'], }, 'WeBWorK::DB::Ex::RecordNotFound' => { isa => 'WeBWorK::DB::Ex', fields => ['type', 'key'], }, 'WeBWorK::DB::Ex::DependencyNotFound' => { isa => 'WeBWorK::DB::Ex::RecordNotFound', }, 'WeBWorK::DB::Ex::TableMissing' => { isa => 'WeBWorK::DB::Ex', description =>"missing table", }, ); ################################################################################ # constructor ################################################################################ =head1 CONSTRUCTOR =over =item new($dbLayout) The C method creates a DB object and brings up the underlying schema/driver structure according to the hash referenced by C<$dbLayout>. =back =head2 C<$dbLayout> Format C<$dbLayout> is a hash reference consisting of items keyed by table names. The value of each item is a reference to a hash containing the following items: =over =item record The name of a perl module to use for representing the data in a record. =item schema The name of a perl module to use for access to the table. =item driver The name of a perl module to use for access to the data source. =item source The location of the data source that should be used by the driver module. Depending on the driver, this may be a path, a url, or a DBI spec. =item params A reference to a hash containing extra information needed by the schema. Some schemas require parameters, some do not. Consult the documentation for the schema in question. =back For each table defined in C<$dbLayout>, C loads the record, schema, and driver modules. It the schema module's C method lists the current table (or contains the string "*") and the output of the schema and driver modules' C