[LON-CAPA-cvs] cvs: loncom /interface loncoursedata.pm
matthew
lon-capa-cvs@mail.lon-capa.org
Mon, 17 Mar 2003 22:52:04 -0000
This is a MIME encoded message
--matthew1047941524
Content-Type: text/plain
matthew Mon Mar 17 17:52:04 2003 EDT
Modified files:
/loncom/interface loncoursedata.pm
Log:
&get_current_state has been rewritten to use MySQL via lonmysql.pm. Lots
of comments and POD documentation and a heck of a lot of new code. Some
of it may even make sense!
--matthew1047941524
Content-Type: text/plain
Content-Disposition: attachment; filename="matthew-20030317175204.txt"
Index: loncom/interface/loncoursedata.pm
diff -u loncom/interface/loncoursedata.pm:1.56 loncom/interface/loncoursedata.pm:1.57
--- loncom/interface/loncoursedata.pm:1.56 Wed Mar 5 09:39:08 2003
+++ loncom/interface/loncoursedata.pm Mon Mar 17 17:52:04 2003
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: loncoursedata.pm,v 1.56 2003/03/05 14:39:08 matthew Exp $
+# $Id: loncoursedata.pm,v 1.57 2003/03/17 22:52:04 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -52,6 +52,8 @@
use Apache::Constants qw(:common :http);
use Apache::lonnet();
use Apache::lonhtmlcommon;
+use Time::HiRes;
+use Apache::lonmysql;
use HTML::TokeParser;
use GDBM_File;
@@ -1524,7 +1526,7 @@
Returns a reference to a hash as described by $values. $values is
assumed to be the result of
- join(':',map {&Apache::lonnet::escape($_)} %orighash;
+ join(':',map {&Apache::lonnet::escape($_)} %orighash);
This is a helper function for get_current_state.
@@ -1545,7 +1547,829 @@
=pod
-=item &get_current_state($sname,$sdom,$symb,$courseid);
+=head1 LOCAL DATA CACHING SUBROUTINES
+
+The local caching is done using MySQL. There is no fall-back implementation
+if MySQL is not running.
+
+The programmers interface is to call &get_current_state() or some other
+primary interface subroutine (described below). The internals of this
+storage system are documented here.
+
+There are six tables used to store student performance data (the results of
+a dumpcurrent). Each of these tables is created in MySQL with a name of
+$courseid_*****, where ***** is 'symb', 'part', or whatever is appropriate
+for the table. The tables and their purposes are described below.
+
+Some notes before we get started.
+
+Each table must have a PRIMARY KEY, which is a column or set of columns which
+will serve to uniquely identify a row of data. NULL is not allowed!
+
+INDEXes work best on integer data.
+
+JOIN is used to combine data from many tables into one output.
+
+lonmysql.pm is used for some of the interface, specifically the table creation
+calls. The inserts are done in bulk by directly calling the database handler.
+The SELECT ... JOIN statement used to retrieve the data does not have an
+interface in lonmysql.pm and I shudder at the thought of writing one.
+
+=head3 Table Descriptions
+
+=over 4
+
+=item $symb_table
+
+The symb_table has two columns. The first is a 'symb_id' and the second
+is the text name for the 'symb' (limited to 64k). The 'symb_id' is generated
+automatically by MySQL so inserts should be done on this table with an
+empty first element. This table has its PRIMARY KEY on the 'symb_id'.
+
+=item $part_table
+
+The part_table has two columns. The first is a 'part_id' and the second
+is the text name for the 'part' (limited to 100 characters). The 'part_id' is
+generated automatically by MySQL so inserts should be done on this table with
+an empty first element. This table has its PRIMARY KEY on the 'part' (100
+characters) and a KEY on 'part_id'.
+
+=item $student_table
+
+The student_table has two columns. The first is a 'student_id' and the second
+is the text description of the 'student' (typically username:domain) (less
+than 100 characters). The 'student_id' is automatically generated by MySQL.
+The use of the name 'student_id' is loaded, I know, but this ID is used ONLY
+internally to the MySQL database and is not the same as the students ID
+(stored in the students environment). This table has its PRIMARY KEY on the
+'student' (100 characters).
+
+=item $updatetime_table
+
+The updatetime_table has two columns. The first is 'student' (100 characters,
+typically username:domain). The second is 'updatetime', which is an unsigned
+integer, NOT a MySQL date. This table has its PRIMARY KEY on 'student' (100
+characters).
+
+=item $performance_table
+
+The performance_table has 9 columns. The first three are 'symb_id',
+'student_id', and 'part_id'. These comprise the PRIMARY KEY for this table
+and are directly related to the $symb_table, $student_table, and $part_table
+described above. MySQL does better indexing on numeric items than text,
+so we use these three "index tables". The remaining columns are
+'solved', 'tries', 'awarded', 'award', 'awarddetail', and 'timestamp'.
+These are either the MySQL type TINYTEXT or various integers ('tries' and
+'timestamp'). This table has KEYs of 'student_id' and 'symb_id'.
+For use of this table, see the functions described below.
+
+=item $parameters_table
+
+The parameters_table holds the data that does not fit neatly into the
+performance_table. The parameters table has four columns: 'symb_id',
+'student_id', 'parameter', and 'value'. 'symb_id', 'student_id', and
+'parameter' comprise the PRIMARY KEY for this table. 'parameter' is
+limited to 255 characters. 'value' is limited to 64k characters.
+
+=back
+
+=head3 Important Subroutines
+
+Here is a brief overview of the subroutines which are likely to be of
+interest:
+
+=over 4
+
+=item &get_current_state(): programmers interface.
+
+=item &init_dbs(): table creation
+
+=item &update_student_data(): data storage calls
+
+=item &get_student_data_from_performance_cache(): data retrieval
+
+=back
+
+=head3 Main Documentation
+
+=over 4
+
+=cut
+
+################################################
+################################################
+
+################################################
+################################################
+{
+
+my $current_course ='';
+my $symb_table;
+my $part_table;
+my $student_table;
+my $updatetime_table;
+my $performance_table;
+my $parameters_table;
+
+################################################
+################################################
+
+=pod
+
+=item &setup_table_names()
+
+input: course id
+
+output: none
+
+Sets the package variables for the MySQL table names:
+
+=over 4
+
+=item $symb_table
+
+=item $part_table
+
+=item $student_table
+
+=item $updatetime_table
+
+=item $performance_table
+
+=item $parameters_table
+
+=back
+
+=cut
+
+################################################
+################################################
+sub setup_table_names {
+ my $courseid = shift;
+ if (! defined($courseid)) {
+ $courseid = $ENV{'request.course.id'};
+ }
+ #
+ # Set up database names
+ my $base_id = $courseid;
+ $symb_table = $base_id.'_'.'symb';
+ $part_table = $base_id.'_'.'part';
+ $student_table = $base_id.'_'.'student';
+ $updatetime_table = $base_id.'_'.'updatetime';
+ $performance_table = $base_id.'_'.'performance';
+ $parameters_table = $base_id.'_'.'parameters';
+ return;
+}
+
+################################################
+################################################
+
+=pod
+
+=item &init_dbs()
+
+Input: course id
+
+Output: 0 on success, positive integer on error
+
+This routine issues the calls to lonmysql to create the tables used to
+store student data.
+
+=cut
+
+################################################
+################################################
+sub init_dbs {
+ my $courseid = shift;
+ &setup_table_names($courseid);
+ #
+ # Note - changes to this table must be reflected in the code that
+ # stores the data (calls &Apache::lonmysql::store_row with this table
+ # id
+ my $symb_table_def = {
+ id => $symb_table,
+ permanent => 'no',
+ columns => [{ name => 'symb_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL',
+ auto_inc => 'yes', },
+ { name => 'symb',
+ type => 'MEDIUMTEXT',
+ restrictions => 'NOT NULL'},
+ ],
+ 'PRIMARY KEY' => ['symb_id'],
+ };
+ #
+ my $part_table_def = {
+ id => $part_table,
+ permanent => 'no',
+ columns => [{ name => 'part_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL',
+ auto_inc => 'yes', },
+ { name => 'part',
+ type => 'VARCHAR(100)',
+ restrictions => 'NOT NULL'},
+ ],
+ 'PRIMARY KEY' => ['part (100)'],
+ 'KEY' => [{ columns => ['part_id']},],
+ };
+ #
+ my $student_table_def = {
+ id => $student_table,
+ permanent => 'no',
+ columns => [{ name => 'student_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL',
+ auto_inc => 'yes', },
+ { name => 'student',
+ type => 'VARCHAR(100)',
+ restrictions => 'NOT NULL'},
+ ],
+ 'PRIMARY KEY' => ['student (100)'],
+ 'KEY' => [{ columns => ['student_id']},],
+ };
+ #
+ my $updatetime_table_def = {
+ id => $updatetime_table,
+ permanent => 'no',
+ columns => [{ name => 'student',
+ type => 'VARCHAR(100)',
+ restrictions => 'NOT NULL UNIQUE',},
+ { name => 'updatetime',
+ type => 'INT UNSIGNED',
+ restrictions => 'NOT NULL' },
+ ],
+ 'PRIMARY KEY' => ['student (100)'],
+ };
+ #
+ my $performance_table_def = {
+ id => $performance_table,
+ permanent => 'no',
+ columns => [{ name => 'symb_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL' },
+ { name => 'student_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL' },
+ { name => 'part_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL' },
+ { name => 'solved',
+ type => 'TINYTEXT' },
+ { name => 'tries',
+ type => 'SMALLINT UNSIGNED' },
+ { name => 'awarded',
+ type => 'TINYTEXT' },
+ { name => 'award',
+ type => 'TINYTEXT' },
+ { name => 'awarddetail',
+ type => 'TINYTEXT' },
+ { name => 'timestamp',
+ type => 'INT UNSIGNED'},
+ ],
+ 'PRIMARY KEY' => ['symb_id','student_id','part_id'],
+ 'KEY' => [{ columns=>['student_id'] },
+ { columns=>['symb_id'] },],
+ };
+ #
+ my $parameters_table_def = {
+ id => $parameters_table,
+ permanent => 'no',
+ columns => [{ name => 'symb_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL' },
+ { name => 'student_id',
+ type => 'MEDIUMINT UNSIGNED',
+ restrictions => 'NOT NULL' },
+ { name => 'parameter',
+ type => 'TINYTEXT',
+ restrictions => 'NOT NULL' },
+ { name => 'value',
+ type => 'MEDIUMTEXT' },
+ ],
+ 'PRIMARY KEY' => ['symb_id','student_id','parameter (255)'],
+ };
+ #
+ # Create the tables
+ my $tableid;
+ $tableid = &Apache::lonmysql::create_table($symb_table_def);
+ if (! defined($tableid)) {
+ &Apache::lonnet::logthis("error creating symb_table: ".
+ &Apache::lonmysql::get_error());
+ return 1;
+ }
+ #
+ $tableid = &Apache::lonmysql::create_table($part_table_def);
+ if (! defined($tableid)) {
+ &Apache::lonnet::logthis("error creating part_table: ".
+ &Apache::lonmysql::get_error());
+ return 2;
+ }
+ #
+ $tableid = &Apache::lonmysql::create_table($student_table_def);
+ if (! defined($tableid)) {
+ &Apache::lonnet::logthis("error creating student_table: ".
+ &Apache::lonmysql::get_error());
+ return 3;
+ }
+ #
+ $tableid = &Apache::lonmysql::create_table($updatetime_table_def);
+ if (! defined($tableid)) {
+ &Apache::lonnet::logthis("error creating updatetime_table: ".
+ &Apache::lonmysql::get_error());
+ return 4;
+ }
+ #
+ $tableid = &Apache::lonmysql::create_table($performance_table_def);
+ if (! defined($tableid)) {
+ &Apache::lonnet::logthis("error creating preformance_table: ".
+ &Apache::lonmysql::get_error());
+ return 5;
+ }
+ #
+ $tableid = &Apache::lonmysql::create_table($parameters_table_def);
+ if (! defined($tableid)) {
+ &Apache::lonnet::logthis("error creating parameters_table: ".
+ &Apache::lonmysql::get_error());
+ return 6;
+ }
+ return 0;
+}
+
+################################################
+################################################
+
+=pod
+
+=item &get_part_id()
+
+Get the MySQL id of a problem part string.
+
+Input: $part
+
+Output: undef on error, integer $part_id on success.
+
+=item &get_part()
+
+Get the string describing a part from the MySQL id of the problem part.
+
+Input: $part_id
+
+Output: undef on error, $part string on success.
+
+=cut
+
+################################################
+################################################
+
+my %ids_by_part;
+my %parts_by_id;
+
+sub get_part_id {
+ my ($part) = @_;
+ if (! exists($ids_by_part{$part})) {
+ &Apache::lonmysql::store_row($part_table,[undef,$part]);
+ undef(%ids_by_part);
+ my @Result = &Apache::lonmysql::get_rows($part_table);
+ foreach (@Result) {
+ $ids_by_part{$_->[1]}=$_->[0];
+ }
+ }
+ return $ids_by_part{$part} if (exists($ids_by_part{$part}));
+ return undef; # error
+}
+
+sub get_part {
+ my ($part_id) = @_;
+ if (! exists($parts_by_id{$part_id}) ||
+ ! defined($parts_by_id{$part_id}) ||
+ $parts_by_id{$part_id} eq '') {
+ my @Result = &Apache::lonmysql::get_rows($part_table);
+ foreach (@Result) {
+ $parts_by_id{$_->[0]}=$_->[1];
+ }
+ }
+ return $parts_by_id{$part_id} if(exists($parts_by_id{$part_id}));
+ return undef; # error
+}
+
+################################################
+################################################
+
+=pod
+
+=item &get_symb_id()
+
+Get the MySQL id of a symb.
+
+Input: $symb
+
+Output: undef on error, integer $symb_id on success.
+
+=item &get_symb()
+
+Get the symb associated with a MySQL symb_id.
+
+Input: $symb_id
+
+Output: undef on error, $symb on success.
+
+=cut
+
+################################################
+################################################
+
+my %ids_by_symb;
+my %symbs_by_id;
+
+sub get_symb_id {
+ my ($symb) = @_;
+ if (! exists($ids_by_symb{$symb})) {
+ &Apache::lonmysql::store_row($symb_table,[undef,$symb]);
+ undef(%ids_by_symb);
+ my @Result = &Apache::lonmysql::get_rows($symb_table);
+ foreach (@Result) {
+ $ids_by_symb{$_->[1]}=$_->[0];
+ }
+ }
+ return $ids_by_symb{$symb} if(exists( $ids_by_symb{$symb}));
+ return undef; # error
+}
+
+sub get_symb {
+ my ($symb_id) = @_;
+ if (! exists($symbs_by_id{$symb_id}) ||
+ ! defined($symbs_by_id{$symb_id}) ||
+ $symbs_by_id{$symb_id} eq '') {
+ my @Result = &Apache::lonmysql::get_rows($symb_table);
+ foreach (@Result) {
+ $symbs_by_id{$_->[0]}=$_->[1];
+ }
+ }
+ return $symbs_by_id{$symb_id} if(exists( $symbs_by_id{$symb_id}));
+ return undef; # error
+}
+
+################################################
+################################################
+
+=pod
+
+=item &get_student_id()
+
+Get the MySQL id of a student.
+
+Input: $sname, $dom
+
+Output: undef on error, integer $student_id on success.
+
+=item &get_student()
+
+Get student username:domain associated with the MySQL student_id.
+
+Input: $student_id
+
+Output: undef on error, string $student (username:domain) on success.
+
+=cut
+
+################################################
+################################################
+
+my %ids_by_student;
+my %students_by_id;
+
+sub get_student_id {
+ my ($sname,$sdom) = @_;
+ my $student = $sname.':'.$sdom;
+ if (! exists($ids_by_student{$student})) {
+ &Apache::lonmysql::store_row($student_table,[undef,$student]);
+ undef(%ids_by_student);
+ my @Result = &Apache::lonmysql::get_rows($student_table);
+ foreach (@Result) {
+ $ids_by_student{$_->[1]}=$_->[0];
+ }
+ }
+ return $ids_by_student{$student} if(exists( $ids_by_student{$student}));
+ return undef; # error
+}
+
+sub get_student {
+ my ($student_id) = @_;
+ if (! exists($students_by_id{$student_id}) ||
+ ! defined($students_by_id{$student_id}) ||
+ $students_by_id{$student_id} eq '') {
+ my @Result = &Apache::lonmysql::get_rows($student_table);
+ foreach (@Result) {
+ $students_by_id{$_->[0]}=$_->[1];
+ }
+ }
+ return $students_by_id{$student_id} if(exists($students_by_id{$student_id}));
+ return undef; # error
+}
+
+################################################
+################################################
+
+=pod
+
+=item &update_student_data()
+
+Input: $sname, $sdom, $courseid
+
+Output: $returnstatus, \%student_data
+
+$returnstatus is a string describing any errors that occured. 'okay' is the
+default.
+\%student_data is the data returned by a call to lonnet::currentdump.
+
+This subroutine loads a students data using lonnet::currentdump and inserts
+it into the MySQL database. The inserts are done on two tables,
+$performance_table and $parameters_table. $parameters_table holds the data
+that is not included in $performance_table. See the description of
+$performance_table elsewhere in this file. The INSERT calls are made
+directly by this subroutine, not through lonmysql because we do a 'bulk'
+insert which takes advantage of MySQLs non-SQL compliant INSERT command to
+insert multiple rows at a time. If anything has gone wrong during this
+process, $returnstatus is updated with a description of the error and
+\%student_data is returned.
+
+Notice we do not insert the data and immediately query it. This means it
+is possible for there to be data returned this first time that is not
+available the second time. CYA.
+
+=cut
+
+################################################
+################################################
+sub update_student_data {
+ my ($sname,$sdom,$courseid) = @_;
+ #
+ my $student_id = &get_student_id($sname,$sdom);
+ my $student = $sname.':'.$sdom;
+ #
+ my $returnstatus = 'okay';
+ #
+ # Set up database names
+ &setup_table_names($courseid);
+ #
+ # Download students data
+ my $time_of_retrieval = time;
+ my @tmp = &Apache::lonnet::currentdump($courseid,$sdom,$sname);
+ if ((scalar(@tmp) > 0) && ($tmp[0] =~ /^error:/)) {
+ &Apache::lonnet::logthis('error getting data for '.
+ $sname.':'.$sdom.' in course '.$courseid.
+ ':'.$tmp[0]);
+ $returnstatus = 'error getting data';
+ return $returnstatus;
+ }
+ if (scalar(@tmp) < 1) {
+ return ('no data',undef);
+ }
+ my %student_data = @tmp;
+ #
+ # Remove all of the students data from the table
+ &Apache::lonmysql::remove_from_table($performance_table,'student_id',
+ $student_id);
+ #
+ # Store away the data
+ #
+ my $starttime = Time::HiRes::time;
+ my $elapsed = 0;
+ my $rows_stored;
+ my $store_parameters_command = 'INSERT INTO '.$parameters_table.
+ ' VALUES ';
+ my $store_performance_command = 'INSERT INTO '.$performance_table.
+ ' VALUES ';
+ my $dbh = &Apache::lonmysql::get_dbh();
+ return 'error' if (! defined($dbh));
+ while (my ($current_symb,$param_hash) = each(%student_data)) {
+ #
+ # make sure the symb is set up properly
+ my $symb_id = &get_symb_id($current_symb);
+ #
+ # Load data into the tables
+ while (my ($parameter,$value) = each (%$param_hash)) {
+ my $newstring;
+ if ($parameter !~ /(timestamp|resource\.(.*)\.(solved|tries|awarded|award|awarddetail|previous))/) {
+ $newstring = "('".join("','",
+ $symb_id,$student_id,
+ $parameter,$value)."'),";
+ if ($newstring !~ /''/) {
+ $store_parameters_command .= $newstring;
+ $rows_stored++;
+ }
+ }
+ next if ($parameter !~ /^resource\.(.*)\.solved$/);
+ #
+ my $part = $1;
+ my $part_id = &get_part_id($part);
+ next if (!defined($part_id));
+ my $solved = $value;
+ my $tries = $param_hash->{'resource.'.$part.'.tries'};
+ my $awarded = $param_hash->{'resource.'.$part.'.awarded'};
+ my $award = $param_hash->{'resource.'.$part.'.award'};
+ my $awarddetail = $param_hash->{'resource.'.$part.'.awarddetail'};
+ my $timestamp = $param_hash->{'timestamp'};
+ $solved = '' if (! defined($awarded));
+ $tries = '' if (! defined($tries));
+ $awarded = '' if (! defined($awarded));
+ $award = '' if (! defined($award));
+ $awarddetail = '' if (! defined($awarddetail));
+ $newstring = "('".join("','",$symb_id,$student_id,$part_id,
+ $solved,$tries,$awarded,$award,
+ $awarddetail,$timestamp)."'),";
+ $store_performance_command .= $newstring;
+ $rows_stored++;
+ }
+ }
+ chop $store_parameters_command;
+ chop $store_performance_command;
+ my $start = Time::HiRes::time;
+ $dbh->do($store_parameters_command);
+ if ($dbh->err()) {
+ &Apache::lonnet::logthis(' bigass insert error:'.$dbh->errstr());
+ &Apache::lonnet::logthis('command = '.$store_performance_command);
+ $returnstatus = 'error: unable to insert parameters into database';
+ return $returnstatus,\%student_data;
+ }
+ $dbh->do($store_performance_command);
+ if ($dbh->err()) {
+ &Apache::lonnet::logthis(' bigass insert error:'.$dbh->errstr());
+ &Apache::lonnet::logthis('command = '.$store_parameters_command);
+ $returnstatus = 'error: unable to insert performance into database';
+ return $returnstatus,\%student_data;
+ }
+ $elapsed += Time::HiRes::time - $start;
+ #
+ # Set the students update time
+ &Apache::lonmysql::replace_row($updatetime_table,
+ [$student,$time_of_retrieval]);
+ &Apache::lonnet::logthis('store took: '.(Time::HiRes::time - $starttime).' for '.$rows_stored);
+ &Apache::lonnet::logthis('mysql store took: '.$elapsed.' for '.$rows_stored);
+ return ($returnstatus,\%student_data);
+}
+
+################################################
+################################################
+
+=pod
+
+=item &ensure_current_data()
+
+Input: $sname, $sdom, $courseid
+
+Output: $status, $data
+
+This routine ensures the data for a given student is up to date. It calls
+&init_dbs() if the tables do not exist. The $updatetime_table is queried
+to determine the time of the last update. If the students data is out of
+date, &update_student_data() is called. The return values from the call
+to &update_student_data() are returned.
+
+=cut
+
+################################################
+################################################
+sub ensure_current_data {
+ my ($sname,$sdom,$courseid) = @_;
+ my $status = 'okay'; # return value
+ #
+ &setup_table_names($courseid);
+ #
+ # if the tables do not exist, make them
+ my @CurrentTable = &Apache::lonmysql::tables_in_db();
+ my ($found_symb,$found_student,$found_part,$found_update,
+ $found_performance,$found_parameters);
+ foreach (@CurrentTable) {
+ $found_symb = 1 if ($_ eq $symb_table);
+ $found_student = 1 if ($_ eq $student_table);
+ $found_part = 1 if ($_ eq $part_table);
+ $found_update = 1 if ($_ eq $updatetime_table);
+ $found_performance = 1 if ($_ eq $performance_table);
+ $found_parameters = 1 if ($_ eq $parameters_table);
+ }
+ if (!$found_symb || !$found_update ||
+ !$found_student || !$found_part ||
+ !$found_performance || !$found_parameters) {
+ if (&init_dbs($courseid)) {
+ return 'error';
+ }
+ }
+ #
+ # Get the update time for the user
+ my $updatetime = 0;
+ my $modifiedtime = 1;
+ #
+ my $student = $sname.':'.$sdom;
+ my @Result = &Apache::lonmysql::get_rows($updatetime_table,
+ "student ='$student'");
+ my $data = undef;
+ if (@Result) {
+ $updatetime = $Result[0]->[1];
+ }
+ if ($modifiedtime > $updatetime) {
+ ($status,$data) = &update_student_data($sname,$sdom,$courseid);
+ }
+ return ($status,$data);
+}
+
+################################################
+################################################
+
+=pod
+
+=item &get_student_data_from_performance_cache()
+
+Input: $sname, $sdom, $symb, $courseid
+
+Output: hash reference containing the data for the given student.
+If $symb is undef, all the students data is returned.
+
+This routine is the heart of the local caching system. See the description
+of $performance_table, $symb_table, $student_table, and $part_table. The
+main task is building the MySQL request. The tables appear in the request
+in the order in which they should be parsed by MySQL. When searching
+on a student the $student_table is used to locate the 'student_id'. All
+rows in $performance_table which have a matching 'student_id' are returned,
+with data from $part_table and $symb_table which match the entries in
+$performance_table, 'part_id' and 'symb_id'. When searching on a symb,
+the $symb_table is processed first, with matching rows grabbed from
+$performance_table and filled in from $part_table and $student_table in
+that order.
+
+Running 'EXPLAIN ' on the 'SELECT' statements generated can be quite
+interesting, especially if you play with the order the tables are listed.
+
+=cut
+
+################################################
+################################################
+sub get_student_data_from_performance_cache {
+ my ($sname,$sdom,$symb,$courseid)=@_;
+ my $student = $sname.':'.$sdom if (defined($sname) && defined($sdom));
+ &setup_table_names();
+ #
+ # Return hash
+ my $studentdata;
+ #
+ my $dbh = &Apache::lonmysql::get_dbh();
+ my $request = "SELECT ".
+ "d.symb,c.part,a.solved,a.tries,a.awarded,a.award,a.awarddetail,".
+ "a.timestamp ";
+ if (defined($student)) {
+ $request .= "FROM $student_table AS b ".
+ "LEFT JOIN $performance_table AS a ON b.student_id=a.student_id ".
+ "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ".
+ "LEFT JOIN $symb_table AS d ON d.symb_id = a.symb_id ".
+ "WHERE student='$student'";
+ if (defined($symb) && $symb ne '') {
+ $request .= " AND d.symb='".$dbh->quote($symb)."'";
+ }
+ } elsif (defined($symb) && $symb ne '') {
+ $request .= "FROM $symb_table as d ".
+ "LEFT JOIN $performance_table AS a ON d.symb_id=a.symb_id ".
+ "LEFT JOIN $part_table AS c ON c.part_id = a.part_id ".
+ "LEFT JOIN $student_table AS b ON b.student_id = a.student_id ".
+ "WHERE symb='".$dbh->quote($symb)."'";
+ }
+ my $starttime = Time::HiRes::time;
+ my $rows_retrieved = 0;
+ my $sth = $dbh->prepare($request);
+ $sth->execute();
+ if ($sth->err()) {
+ &Apache::lonnet::logthis("Unable to execute MySQL request:");
+ &Apache::lonnet::logthis("\n".$request."\n");
+ &Apache::lonnet::logthis("error is:".$sth->errstr());
+ return undef;
+ }
+ foreach my $row (@{$sth->fetchall_arrayref}) {
+ $rows_retrieved++;
+ my ($symb,$part,$solved,$tries,$awarded,$award,$awarddetail,$time) =
+ (@$row);
+ my $base = 'resource.'.$part;
+ $studentdata->{$symb}->{$base.'.solved'} = $solved;
+ $studentdata->{$symb}->{$base.'.tries'} = $tries;
+ $studentdata->{$symb}->{$base.'.awarded'} = $awarded;
+ $studentdata->{$symb}->{$base.'.award'} = $award;
+ $studentdata->{$symb}->{$base.'.awarddetail'} = $awarddetail;
+ $studentdata->{$symb}->{'timestamp'} = $time if (defined($time) && $time ne '');
+ }
+ &Apache::lonnet::logthis('retrieve took: '.(Time::HiRes::time - $starttime).' for '.$rows_retrieved);
+ return $studentdata;
+}
+
+################################################
+################################################
+
+=pod
+
+=item &get_current_state()
+
+Input: $sname,$sdom,$symb,$courseid
+
+Output: Described below
Retrieve the current status of a students performance. $sname and
$sdom are the only required parameters. If $symb is undef the results
@@ -1567,7 +2391,7 @@
)
is returned.
-If no data is found for $symb, or if the student has not performance data,
+If no data is found for $symb, or if the student has no performance data,
an empty list is returned.
=cut
@@ -1576,98 +2400,36 @@
################################################
sub get_current_state {
my ($sname,$sdom,$symb,$courseid,$forcedownload)=@_;
+ if ($current_course ne $courseid) {
+ # Clear out variables
+ undef(%ids_by_part);
+ undef(%parts_by_id);
+ undef(%ids_by_symb);
+ undef(%symbs_by_id);
+ undef(%ids_by_student);
+ undef(%students_by_id);
+ $current_course = $courseid;
+ }
return () if (! defined($sname) || ! defined($sdom));
#
$courseid = $ENV{'request.course.id'} if (! defined($courseid));
#
-# my $cachefilename = $Apache::lonnet::tmpdir.$ENV{'user.name'}.'_'.
-# $ENV{'user.domain'}.'_'.
-# $courseid.'_student_data.db';
- my $cachefilename = $Apache::lonnet::tmpdir.$ENV{'user.name'}.'_'.
- $ENV{'user.domain'}.'_'.
- $courseid.'_'.
- $sname.'_'.$sdom.
- '_student_data.db';
- my %cache;
- #
- my %student_data; # return values go here
- #
- my $updatetime = 0;
- my $key = &Apache::lonnet::escape($sname).':'.
- &Apache::lonnet::escape($sdom).':';
- # Open the cache file
- if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_READER(),0640)) {
- if (exists($cache{$key.'time'})) {
- $updatetime = $cache{$key.'time'};
- }
- untie(%cache);
- }
- # timestamp/devalidation check should go here.
- my $modifiedtime = 1;
- # Take whatever steps are neccessary at this point to give $modifiedtime a
- # new value
+ my ($status,$data) = &ensure_current_data($sname,$sdom,$courseid);
#
- if (($updatetime < $modifiedtime) ||
- (defined($forcedownload) && $forcedownload)) {
- # Get all the students current data
- my $time_of_retrieval = time;
- my @tmp = &Apache::lonnet::currentdump($courseid,$sdom,$sname);
- if ((scalar(@tmp) > 0) && ($tmp[0] =~ /^error:/)) {
- &Apache::lonnet::logthis('error getting data for '.
- $sname.':'.$sdom.' in course '.$courseid.
- ':'.$tmp[0]);
- return ();
- }
- %student_data = @tmp;
- #
- # Store away the data
- #
- # The cache structure is colon deliminated.
- # $uname:$udom:time => timestamp
- # $uname:$udom:$symb => $parm1:$val1:$parm2:$val2 ...
- #
- # BEWARE: The colons are NOT escaped so can search with escaped
- # keys instead of unescaping every key.
- #
- if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_WRCREAT(),0640)) {
- while (my ($current_symb,$param_hash) = each(%student_data)) {
- my @Parameters = %{$param_hash};
- my $value = join(':',map { &Apache::lonnet::escape($_); }
- @Parameters);
- # Store away the values
- $cache{$key.&Apache::lonnet::escape($current_symb)}=$value;
- }
- $cache{$key.'time'}=$time_of_retrieval;
- untie(%cache);
- }
+ if (defined($data)) {
+ return %$data;
+ } elsif ($status eq 'no data') {
+ return ();
} else {
- if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_READER(),0640)) {
- if (defined($symb)) {
- my $searchkey = $key.&Apache::lonnet::escape($symb);
- if (exists($cache{$searchkey})) {
- $student_data{$symb} = &make_into_hash($cache{$searchkey});
- }
- } else {
- my $searchkey = '^'.$key.'(.*)$';#'
- while (my ($testkey,$params)=each(%cache)) {
- if ($testkey =~ /$searchkey/) { # \Q \E? May be necc.
- my $tmpsymb = $1;
- next if ($tmpsymb =~ 'time');
- $student_data{&Apache::lonnet::unescape($tmpsymb)} =
- &make_into_hash($params);
- }
- }
- }
- untie(%cache);
+ if ($status ne 'okay' && $status ne '') {
+ &Apache::lonnet::logthis('status = '.$status);
+ return ();
}
+ my $returnhash = &get_student_data_from_performance_cache($sname,$sdom,
+ $symb,$courseid);
+ return %$returnhash if (defined($returnhash));
}
- if (! defined($symb)) {
- return %student_data;
- } elsif (exists($student_data{$symb})) {
- return %{$student_data{$symb}};
- } else {
- return ();
- }
+ return ();
}
################################################
@@ -1675,6 +2437,24 @@
=pod
+=back
+
+=item End of Local Data Caching Subroutines
+
+=cut
+
+################################################
+################################################
+
+
+}
+################################################
+################################################
+
+=pod
+
+=head3 Classlist Subroutines
+
=item &get_classlist();
Retrieve the classist of a given class or of the current class. Student
@@ -1713,7 +2493,7 @@
$cid = $cid || $ENV{'request.course.id'};
$cdom = $cdom || $ENV{'course.'.$cid.'.domain'};
$cnum = $cnum || $ENV{'course.'.$cid.'.num'};
- my $now = time;
+ my $now = time;
#
my %classlist=&Apache::lonnet::dump('classlist',$cdom,$cnum);
while (my ($student,$info) = each(%classlist)) {
--matthew1047941524--