[system] / trunk / webwork2 / bin / wwdb_check Repository:
ViewVC logotype

View of /trunk/webwork2/bin/wwdb_check

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5319 - (download) (annotate)
Mon Aug 13 22:59:59 2007 UTC (5 years, 10 months ago) by sh002i
File size: 26959 byte(s)
updated copyright dates

    1 #!/usr/bin/env perl
    2 ################################################################################
    3 # WeBWorK Online Homework Delivery System
    4 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    5 # $CVSHeader: webwork2/bin/wwdb_check,v 1.7 2006/08/14 17:23:56 sh002i Exp $
    6 # 
    7 # This program is free software; you can redistribute it and/or modify it under
    8 # the terms of either: (a) the GNU General Public License as published by the
    9 # Free Software Foundation; either version 2, or (at your option) any later
   10 # version, or (b) the "Artistic License" which comes with this package.
   11 # 
   12 # This program is distributed in the hope that it will be useful, but WITHOUT
   13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   14 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   15 # Artistic License for more details.
   16 ################################################################################
   17 
   18 =head1 NAME
   19 
   20 wwdb_check - check the schema of an existing WeBWorK database
   21 
   22 =head1 SYNOPSIS
   23 
   24  wwdb_check [-nv] [ COURSE ... ]
   25 
   26 =head1 DESCRIPTION
   27 
   28 Scans an existing WeBWorK database to verify that its structure is correct for
   29 version 0 of the database structure. Version 0 refers to the last version before
   30 automatic database upgrading was added to WeBWorK. This utility should be run
   31 once after upgrading webwork from version 2.2.x to version 2.3.0.
   32 
   33 Once any inconsistencies are fixed using this utility, F<wwdb_upgrade> should be
   34 run to affect automatic database upgrades to the database version appropriate
   35 for the current version of WeBWorK.
   36 
   37 If no courses are listed on the command line, all courses are checked. Checks
   38 for the following:
   39 
   40 =over
   41 
   42 =item *
   43 
   44 Make sure that the appropriate tables exist for each course.
   45 
   46 =item *
   47 
   48 Make sure that the proper columns exist in each table.
   49 
   50 =item *
   51 
   52 Verify that the proper column type is in use for each column.
   53 
   54 =back
   55 
   56 =head1 OPTIONS
   57 
   58 =over
   59 
   60 =item -n
   61 
   62 Don't offer to fix problems, just report them.
   63 
   64 =item -v
   65 
   66 Verbose output.
   67 
   68 =back
   69 
   70 =cut
   71 
   72 use strict;
   73 use warnings;
   74 use Getopt::Std;
   75 use DBI;
   76 use Data::Dumper;
   77 
   78 BEGIN {
   79 	die "WEBWORK_ROOT not found in environment.\n"
   80 		unless exists $ENV{WEBWORK_ROOT};
   81 }
   82 
   83 use lib "$ENV{WEBWORK_ROOT}/lib";
   84 use WeBWorK::CourseEnvironment;
   85 use WeBWorK::Utils qw/runtime_use/;
   86 use WeBWorK::Utils::CourseManagement qw/listCourses/;
   87 
   88 our ($opt_n, $opt_v);
   89 getopts("nv");
   90 
   91 my $noop = sub {};
   92 
   93 if ($opt_n) {
   94 	*maybe_add_table = $noop;
   95 	*maybe_add_field = $noop;
   96 	*maybe_change_field = $noop;
   97 } else {
   98 	*maybe_add_table = \&ask_add_table;
   99 	*maybe_add_field = \&ask_add_field;
  100 	*maybe_change_field = \&ask_change_field;
  101 }
  102 
  103 if ($opt_v) {
  104 	$| = 1;
  105 	*verbose = sub { print STDERR @_ };
  106 } else {
  107 	*verbose = $noop;
  108 }
  109 
  110 use constant DB_VERSION => 0;
  111 
  112 # a random coursename we can grab back out later
  113 #my @chars = ('A'..'Z','a'..'z','0'..'9');
  114 #my $random_courseID = join("", map { $chars[rand(@chars)] } 1..16);
  115 # fixed courseID for "version zero table data"
  116 my $random_courseID = "6SC36NukknC3IT3M";
  117 
  118 my $ce = WeBWorK::CourseEnvironment->new({
  119 	webwork_dir => $ENV{WEBWORK_ROOT},
  120 	courseName => $random_courseID,
  121 });
  122 
  123 my $dbh = DBI->connect(
  124 	$ce->{database_dsn},
  125 	$ce->{database_username},
  126 	$ce->{database_password},
  127 	{
  128 		PrintError => 0,
  129 		RaiseError => 1,
  130 	},
  131 );
  132 
  133 =for comment
  134 
  135  %ww_table_data = (
  136 	 $table => {
  137 		 sql_name => "SQL name for this field, probably contains $random_courseID",
  138 		 field_order => [ ... ],
  139 		 keyfield_order => [ ... ],
  140 		 fields => {
  141 			 $field => {
  142 				 sql_name => "SQL name for this field, possibly overridden",
  143 				 sql_type => "type for this field, from SQL_TYPES in record class",
  144 				 is_keyfield => "boolean, whether or not this field is a keyfield",
  145 			 },
  146 			 ...
  147 		 },
  148 	 },
  149 	 ...
  150  );
  151 
  152 =cut
  153 
  154 # get table data for the current version of webwork
  155 #my %ww_table_data = get_ww_table_data();
  156 #$Data::Dumper::Indent = 1;
  157 #print Dumper(\%ww_table_data);
  158 #exit;
  159 # get static table data for version zero of the database
  160 my %ww_table_data = get_version_zero_ww_table_data();
  161 
  162 my %sql_tables = get_sql_tables();
  163 
  164 if (exists $sql_tables{dbupgrade}) {
  165 	print "A 'dbupgrade' table exists in this database. This suggests that this database may already be upgraded beyond db_version 0. If this is the case, running this utility is not necessary. This utility is only needed to make sure that databases are set up correctly to enter into the automatic upgrade regimen.\n";
  166 	exit unless ask_permission("Go ahead with table checks?", 0);
  167 	delete $sql_tables{dbupgrade};
  168 }
  169 
  170 my @ww_courses = @ARGV;
  171 @ww_courses = listCourses($ce) if not @ww_courses;
  172 
  173 foreach my $ww_course_name (@ww_courses) {
  174 	my $ce2 = WeBWorK::CourseEnvironment->new({
  175 		webwork_dir => $ENV{WEBWORK_ROOT},
  176 		courseName => $ww_course_name,
  177 	});
  178 	
  179 	my @diffs = compare_dbLayouts($ce, $ce2);
  180 	if (@diffs) {
  181 		print "\nThe database layout for course '$ww_course_name' differs from the generic database layout in global.conf. Here's how:\n\n";
  182 		print map("* $_\n", @diffs), "\n";
  183 		next unless ask_permission("Check course '$ww_course_name'?", 0);
  184 	}
  185 	
  186 	print "\nChecking tables for course '$ww_course_name'\n";
  187 	
  188 	foreach my $ww_table_name (keys %ww_table_data) {
  189 		if ($ce2->{dbLayout}{$ww_table_name}{params}{non_native}) {
  190 			verbose("skipping table $ww_table_name for course $ww_course_name -- not a native table.\n");
  191 		} else {
  192 			check_table($ww_course_name, $ww_table_name);
  193 		}
  194 	}
  195 }
  196 
  197 my $qualifier = @ARGV ? " selected" : "";
  198 print "\nDone checking course tables.\n";
  199 print "The following tables exist in the database but are not associated with any$qualifier course:\n\n";
  200 print join("\n", sort keys %sql_tables), "\n\n";
  201 
  202 exit;
  203 
  204 ################################################################################
  205 
  206 sub get_ww_table_data {
  207 	my %result;
  208 	
  209 	foreach my $table (keys %{$ce->{dbLayout}}) {
  210 		my $record_class = $ce->{dbLayout}{$table}{record};
  211 		runtime_use $record_class;
  212 		
  213 		my @fields = $record_class->FIELDS;
  214 		my @types = $record_class->SQL_TYPES;
  215 		my @keyfields = $record_class->KEYFIELDS;
  216 		my %keyfields; @keyfields{@keyfields} = ();
  217 		
  218 		my %field_data;
  219 		
  220 		foreach my $i (0..$#fields) {
  221 			my $field = $fields[$i];
  222 			my $field_sql = $ce->{dbLayout}{$table}{params}{fieldOverride}{$field};
  223 			$field_data{$field}{sql_name} = $field_sql || $field;
  224 			
  225 			my $type = $types[$i];
  226 			$field_data{$field}{sql_type} = $type;
  227 			
  228 			$field_data{$field}{is_keyfield} = exists $keyfields{$field};
  229 		}
  230 		
  231 		$result{$table}{fields} = \%field_data;
  232 		$result{$table}{field_order} = \@fields;
  233 		$result{$table}{keyfield_order} = \@keyfields;
  234 		
  235 		my $table_sql = $ce->{dbLayout}{$table}{params}{tableOverride};
  236 		$result{$table}{sql_name} = $table_sql || $table;
  237 	}
  238 	
  239 	return %result;
  240 }
  241 
  242 sub get_sql_tables {
  243 	my $sql_tables_ref = $dbh->selectcol_arrayref("SHOW TABLES");
  244 	my %sql_tables; @sql_tables{@$sql_tables_ref} = ();
  245 	
  246 	return %sql_tables;
  247 }
  248 
  249 ################################################################################
  250 
  251 sub check_table {
  252 	my ($ww_course_name, $ww_table_name) = @_;
  253 	my $sql_table_name = get_sql_table_name($ww_table_data{$ww_table_name}{sql_name}, $ww_course_name);
  254 	
  255 	verbose("\nChecking '$ww_table_name' table (SQL table '$sql_table_name')\n");
  256 	
  257 	if (exists $sql_tables{$sql_table_name}) {
  258 		check_fields($ww_course_name, $ww_table_name, $sql_table_name);
  259 		delete $sql_tables{$sql_table_name};
  260 	} else {
  261 		print "$sql_table_name: table missing\n";
  262 		my $ww_table_rec = $ww_table_data{$ww_table_name};
  263 		if (maybe_add_table($ww_course_name, $ww_table_name)) {
  264 			check_fields($ww_course_name, $ww_table_name, $sql_table_name);
  265 			delete $sql_tables{$sql_table_name};
  266 		}
  267 	}
  268 }
  269 
  270 sub ask_add_table {
  271 	my ($ww_course_name, $ww_table_name) = @_;
  272 	my $ww_table_rec = $ww_table_data{$ww_table_name};
  273 	my $sql_table_name = get_sql_table_name($ww_table_rec->{sql_name}, $ww_course_name);
  274 	
  275 	my $stmt = create_table_stmt($ww_table_rec, $sql_table_name);
  276 	
  277 	print "\nI can add this table to the database with the following SQL statement:\n";
  278 	print "$stmt\n\n";
  279 	print "If this is an upgraded installation, it is possible that '$ww_course_name' is an old GDBM course. If this is the case, you should probably not add this table, as it won't be used.\n";
  280 	return 0 unless ask_permission("Add table '$sql_table_name'?");
  281 	
  282 	return unless do_handle_error($dbh, $stmt);
  283 	print "Added table '$sql_table_name'.\n\n";
  284 	
  285 	return 1;
  286 }
  287 
  288 sub create_table_stmt {
  289 	my ($ww_table_rec, $sql_table_name) = @_;
  290 	
  291 	#print Dumper($ww_table_rec);
  292 	
  293 	my @field_list;
  294 	
  295 	# generate a column specification for each field
  296 	my @fields = @{$ww_table_rec->{field_order}};
  297 	foreach my $field (@fields) {
  298 		my $ww_field_rec = $ww_table_rec->{fields}{$field};
  299 		my $sql_field_name = $ww_field_rec->{sql_name};
  300 		my $sql_field_type = $ww_field_rec->{sql_type};
  301 		
  302 		push @field_list, "`$sql_field_name` $sql_field_type";
  303 	}
  304 	
  305 	# generate an INDEX specification for each all possible sets of keyfields (i.e. 0+1+2, 1+2, 2)
  306 	my @keyfields = @{$ww_table_rec->{keyfield_order}};
  307 	foreach my $start (0 .. $#keyfields) {
  308 		my @index_components;
  309 		
  310 		foreach my $component (@keyfields[$start .. $#keyfields]) {
  311 			my $ww_field_rec = $ww_table_rec->{fields}{$component};
  312 			my $sql_field_name = $ww_field_rec->{sql_name};
  313 			my $sql_field_type = $ww_field_rec->{sql_type};
  314 			my $length_specifier = ($sql_field_type =~ /int/i) ? "" : "(16)";
  315 			push @index_components, "`$sql_field_name`$length_specifier";
  316 		}
  317 		
  318 		my $index_string = join(", ", @index_components);
  319 		push @field_list, "INDEX ( $index_string )";
  320 	}
  321 	
  322 	my $field_string = join(", ", @field_list);
  323 	my $create_stmt = "CREATE TABLE `$sql_table_name` ( $field_string )";
  324 	
  325 	return $create_stmt;
  326 }
  327 
  328 ################################################################################
  329 
  330 sub check_fields {
  331 	my ($ww_course_name, $ww_table_name, $sql_table_name) = @_;
  332 	
  333 	my $describe_data = $dbh->selectall_hashref("DESCRIBE `$sql_table_name`", 1);
  334 	
  335 	foreach my $ww_field_name (@{$ww_table_data{$ww_table_name}{field_order}}) {
  336 		my $ww_field_rec = $ww_table_data{$ww_table_name}{fields}{$ww_field_name};
  337 		my $sql_field_name = $ww_field_rec->{sql_name};
  338 		my $sql_field_rec = $describe_data->{$sql_field_name};
  339 		
  340 		verbose("Checking '$ww_field_name' field (SQL field '$sql_table_name.$sql_field_name')\n");
  341 		
  342 		#print "$sql_table_name.$sql_field_name:\n";
  343 		#print Dumper($ww_field_rec);
  344 		#print Dumper($sql_field_rec);
  345 		
  346 		if (defined $sql_field_rec) {
  347 			my ($sql_base_type) = $sql_field_rec->{Type} =~ /^([^(]*)/;
  348 			#print $sql_field_rec->{Type}, " => $sql_base_type\n";
  349 			
  350 			my $needs_fixing = 0;
  351 			if ($ww_field_name eq "psvn") {
  352 				
  353 				unless ("int" eq lc($sql_base_type)) {
  354 					$needs_fixing = 1;
  355 					print "$sql_table_name.$sql_field_name: type should be 'int' but appears to be '",
  356 						 lc($sql_base_type), "'\n";
  357 				}
  358 				
  359 				unless (lc($sql_field_rec->{Extra}) =~ /\bauto_increment\b/) {
  360 					$needs_fixing = 1;
  361 					print "$sql_table_name.$sql_field_name: extra should contain 'auto_increment' but appears to be '",
  362 						lc($sql_field_rec->{Extra}), "'\n";
  363 				}
  364 				
  365 				# FIXME instead of checking this, figure out how to use "SHOW INDEXES FROM `$sql_table_name`"
  366 				#unless ("pri" eq lc($sql_field_rec->{Key})) {
  367 				#	$needs_fixing = 1;
  368 				#	print "$sql_table_name.$sql_field_name: key should be 'pri' but appears to be '",
  369 				#		lc($sql_field_rec->{Key}), "'\n";
  370 				#}
  371 				
  372 			} else {
  373 				
  374 				unless (lc($ww_field_rec->{sql_type}) eq lc($sql_base_type)) {
  375 					$needs_fixing = 1;
  376 					print "$sql_table_name.$sql_field_name: type should be '", lc($ww_field_rec->{sql_type}),
  377 						"' but appears to be '", lc($sql_base_type), "'\n";
  378 				}
  379 				
  380 				# FIXME instead of checking this, figure out how to use "SHOW INDEXES FROM `$sql_table_name`"
  381 				#unless ( $ww_field_rec->{is_keyfield} == (lc($sql_field_rec->{Key}) eq "mul") ) {
  382 				#	$needs_fixing = 1;
  383 				#	print "$sql_table_name.$sql_field_name: key should be '",
  384 				#		($ww_field_rec->{is_keyfield} ? "mul" : ""), "' but appears to be '",
  385 				#		lc($sql_field_rec->{Key}), "'\n";
  386 				#}
  387 			}
  388 			
  389 			$needs_fixing and maybe_change_field($ww_course_name, $ww_table_name, $ww_field_name, $sql_base_type);
  390 			
  391 		} else {
  392 			print "$sql_table_name.$sql_field_name: field missing\n";
  393 			maybe_add_field($ww_course_name, $ww_table_name, $ww_field_name);
  394 		}
  395 	}
  396 }
  397 
  398 sub ask_add_field {
  399 	my ($ww_course_name, $ww_table_name, $ww_field_name) = @_;
  400 	my $ww_table_rec = $ww_table_data{$ww_table_name};
  401 	my $sql_table_name = get_sql_table_name($ww_table_rec->{sql_name}, $ww_course_name);
  402 	my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name};
  403 	
  404 	my $stmt = add_field_stmt($ww_table_rec, $ww_field_name, $sql_table_name);
  405 	
  406 	print "\nI can add this field to the database with the following SQL statement:\n";
  407 	print "$stmt\n\n";
  408 	return 0 unless ask_permission("Add field '$sql_table_name.$sql_field_name'?");
  409 	
  410 	return unless do_handle_error($dbh, $stmt);
  411 	print "Added field '$sql_field_name'.\n\n";
  412 	
  413 	return 0;
  414 }
  415 
  416 sub add_field_stmt {
  417 	my ($ww_table_rec, $ww_field_name, $sql_table_name) = @_;
  418 	my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name};
  419 	my $sql_field_type = $ww_table_rec->{fields}{$ww_field_name}{sql_type};
  420 	my $location_modifier = get_location_modifier($ww_table_rec, $ww_field_name);
  421 	
  422 	return "ALTER TABLE `$sql_table_name` ADD COLUMN `$sql_field_name` $sql_field_type $location_modifier";
  423 }
  424 
  425 sub get_location_modifier {
  426 	my ($ww_table_rec, $ww_field_name) = @_;
  427 	
  428 	my $field_index = -1;
  429 	
  430 	for (my $i = 0; $i < @{$ww_table_rec->{field_order}}; $i++) {
  431 		if ($ww_table_rec->{field_order}[$i] eq $ww_field_name) {
  432 			$field_index = $i;
  433 			last;
  434 		}
  435 	}
  436 	
  437 	if ($field_index < 0) {
  438 		die "field '$ww_field_name' not found in field_order (shouldn't happen!)";
  439 	} elsif ($field_index > 0) {
  440 		my $ww_prev_field_name = $ww_table_rec->{field_order}[$field_index-1];
  441 		my $sql_prev_field_name = $ww_table_rec->{fields}{$ww_prev_field_name}{sql_name};
  442 		return "AFTER `$sql_prev_field_name`";
  443 	} else {
  444 		return "FIRST";
  445 	}
  446 }
  447 
  448 sub ask_change_field {
  449 	my ($ww_course_name, $ww_table_name, $ww_field_name, $sql_curr_base_type) = @_;
  450 	my $ww_table_rec = $ww_table_data{$ww_table_name};
  451 	my $sql_table_name = get_sql_table_name($ww_table_rec->{sql_name}, $ww_course_name);
  452 	my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name};
  453 	
  454 	my @stmts = change_field_stmts($ww_table_rec, $ww_field_name, $sql_table_name, $sql_curr_base_type);
  455 	
  456 	my $pl = @stmts == 1 ? "" : "s";
  457 	print "\nI can change this field with the following SQL statement$pl:\n";
  458 	print map("$_\n", @stmts), "\n";
  459 	return 0 unless ask_permission("Change field '$sql_table_name.$sql_field_name'?");
  460 	
  461 	foreach my $stmt (@stmts) {
  462 		return unless do_handle_error($dbh, $stmt);
  463 	}
  464 	print "Changed field '$sql_field_name'.\n\n";
  465 	
  466 	return 0;
  467 }
  468 
  469 sub change_field_stmts {
  470 	my ($ww_table_rec, $ww_field_name, $sql_table_name, $sql_curr_base_type) = @_;
  471 	my $sql_field_name = $ww_table_rec->{fields}{$ww_field_name}{sql_name};
  472 	my $sql_field_type = $ww_table_rec->{fields}{$ww_field_name}{sql_type};
  473 	
  474 	if ($sql_curr_base_type =~ /text/i and $sql_field_type =~ /int/i) {
  475 		return (
  476 			"ALTER TABLE `$sql_table_name` CHANGE COLUMN `$sql_field_name` `$sql_field_name` VARCHAR(255)",
  477 			"ALTER TABLE `$sql_table_name` CHANGE COLUMN `$sql_field_name` `$sql_field_name` $sql_field_type",
  478 		);
  479 	} else {
  480 		return "ALTER TABLE `$sql_table_name` CHANGE COLUMN `$sql_field_name` `$sql_field_name` $sql_field_type";
  481 	}
  482 }
  483 
  484 ################################################################################
  485 
  486 sub get_sql_table_name {
  487 	my ($template, $course_name) = @_;
  488 	
  489 	$template =~ s/$random_courseID/$course_name/g;
  490 	return $template;
  491 }
  492 
  493 sub ask_permission {
  494 	my ($prompt, $default) = @_;
  495 	
  496 	$default = 1 if not defined $default;
  497 	my $options = $default ? "[Y/n]" : "[y/N]";
  498 	
  499 	while (1) {
  500 		print "$prompt $options ";
  501 		my $resp = <STDIN>;
  502 		chomp $resp;
  503 		return $default if $resp eq "";
  504 		return 1 if lc $resp eq "y";
  505 		return 0 if lc $resp eq "n";
  506 		$prompt = 'Please enter "y" or "n".';
  507 	}
  508 }
  509 
  510 # no error => returns true
  511 # error, user says continue => returns false
  512 # error, user says don't continue => returns undef
  513 # error, user says exit => exits
  514 sub do_handle_error {
  515 	my ($dbh, $stmt) = @_;
  516 	
  517 	eval { $dbh->do($stmt) };
  518 	if ($@) {
  519 		print "SQL statment failed. Here is the error message: $@\n";
  520 		return ask_permission("Continue?", 1);
  521 	} else {
  522 		return 1;
  523 	}
  524 }
  525 
  526 sub compare_dbLayouts {
  527 	my ($ce1, $ce2) = @_;
  528 	
  529 	my $dbLayout1 = $ce1->{dbLayoutName};
  530 	my $dbLayout2 = $ce2->{dbLayoutName};
  531 	#warn "Generic: '$dbLayout1' this course: '$dbLayout2'.\n";
  532 	
  533 	# simplisic check for now
  534 	if ($dbLayout1 ne $dbLayout2) {
  535 		return "\$dbLayoutName differs. Generic: '$dbLayout1' this course: '$dbLayout2'. (If you've created"
  536 		. " a modified version of the '$dbLayout1' database layout for use with this course, it's probably"
  537 		. " OK to check this course anyway. Just be sure that any fixes this program proposes are"
  538 		. " appropriate given your modifications.)";
  539 	}
  540 	
  541 	return ();
  542 }
  543 
  544 ################################################################################
  545 
  546 sub get_version_zero_ww_table_data {
  547 	return (
  548 	  'problem_user' => {
  549 		'fields' => {
  550 		  'problem_seed' => {
  551 			'is_keyfield' => '',
  552 			'sql_type' => 'INT',
  553 			'sql_name' => 'problem_seed'
  554 		  },
  555 		  'status' => {
  556 			'is_keyfield' => '',
  557 			'sql_type' => 'TEXT',
  558 			'sql_name' => 'status'
  559 		  },
  560 		  'max_attempts' => {
  561 			'is_keyfield' => '',
  562 			'sql_type' => 'INT',
  563 			'sql_name' => 'max_attempts'
  564 		  },
  565 		  'value' => {
  566 			'is_keyfield' => '',
  567 			'sql_type' => 'INT',
  568 			'sql_name' => 'value'
  569 		  },
  570 		  'last_answer' => {
  571 			'is_keyfield' => '',
  572 			'sql_type' => 'TEXT',
  573 			'sql_name' => 'last_answer'
  574 		  },
  575 		  'source_file' => {
  576 			'is_keyfield' => '',
  577 			'sql_type' => 'TEXT',
  578 			'sql_name' => 'source_file'
  579 		  },
  580 		  'set_id' => {
  581 			'is_keyfield' => 1,
  582 			'sql_type' => 'BLOB',
  583 			'sql_name' => 'set_id'
  584 		  },
  585 		  'problem_id' => {
  586 			'is_keyfield' => 1,
  587 			'sql_type' => 'INT',
  588 			'sql_name' => 'problem_id'
  589 		  },
  590 		  'num_incorrect' => {
  591 			'is_keyfield' => '',
  592 			'sql_type' => 'INT',
  593 			'sql_name' => 'num_incorrect'
  594 		  },
  595 		  'num_correct' => {
  596 			'is_keyfield' => '',
  597 			'sql_type' => 'INT',
  598 			'sql_name' => 'num_correct'
  599 		  },
  600 		  'attempted' => {
  601 			'is_keyfield' => '',
  602 			'sql_type' => 'INT',
  603 			'sql_name' => 'attempted'
  604 		  },
  605 		  'user_id' => {
  606 			'is_keyfield' => 1,
  607 			'sql_type' => 'BLOB',
  608 			'sql_name' => 'user_id'
  609 		  }
  610 		},
  611 		'keyfield_order' => [
  612 		  'user_id',
  613 		  'set_id',
  614 		  'problem_id'
  615 		],
  616 		'field_order' => [
  617 		  'user_id',
  618 		  'set_id',
  619 		  'problem_id',
  620 		  'source_file',
  621 		  'value',
  622 		  'max_attempts',
  623 		  'problem_seed',
  624 		  'status',
  625 		  'attempted',
  626 		  'last_answer',
  627 		  'num_correct',
  628 		  'num_incorrect'
  629 		],
  630 		'sql_name' => '6SC36NukknC3IT3M_problem_user'
  631 	  },
  632 	  'permission' => {
  633 		'fields' => {
  634 		  'permission' => {
  635 			'is_keyfield' => '',
  636 			'sql_type' => 'INT',
  637 			'sql_name' => 'permission'
  638 		  },
  639 		  'user_id' => {
  640 			'is_keyfield' => 1,
  641 			'sql_type' => 'BLOB',
  642 			'sql_name' => 'user_id'
  643 		  }
  644 		},
  645 		'keyfield_order' => [
  646 		  'user_id'
  647 		],
  648 		'field_order' => [
  649 		  'user_id',
  650 		  'permission'
  651 		],
  652 		'sql_name' => '6SC36NukknC3IT3M_permission'
  653 	  },
  654 	  'key' => {
  655 		'fields' => {
  656 		  'timestamp' => {
  657 			'is_keyfield' => '',
  658 			'sql_type' => 'TEXT',
  659 			'sql_name' => 'timestamp'
  660 		  },
  661 		  'user_id' => {
  662 			'is_keyfield' => 1,
  663 			'sql_type' => 'BLOB',
  664 			'sql_name' => 'user_id'
  665 		  },
  666 		  'key' => {
  667 			'is_keyfield' => '',
  668 			'sql_type' => 'TEXT',
  669 			'sql_name' => 'key_not_a_keyword'
  670 		  }
  671 		},
  672 		'keyfield_order' => [
  673 		  'user_id'
  674 		],
  675 		'field_order' => [
  676 		  'user_id',
  677 		  'key',
  678 		  'timestamp'
  679 		],
  680 		'sql_name' => '6SC36NukknC3IT3M_key'
  681 	  },
  682 	  'password' => {
  683 		'fields' => {
  684 		  'password' => {
  685 			'is_keyfield' => '',
  686 			'sql_type' => 'TEXT',
  687 			'sql_name' => 'password'
  688 		  },
  689 		  'user_id' => {
  690 			'is_keyfield' => 1,
  691 			'sql_type' => 'BLOB',
  692 			'sql_name' => 'user_id'
  693 		  }
  694 		},
  695 		'keyfield_order' => [
  696 		  'user_id'
  697 		],
  698 		'field_order' => [
  699 		  'user_id',
  700 		  'password'
  701 		],
  702 		'sql_name' => '6SC36NukknC3IT3M_password'
  703 	  },
  704 	  'problem' => {
  705 		'fields' => {
  706 		  'problem_id' => {
  707 			'is_keyfield' => 1,
  708 			'sql_type' => 'INT',
  709 			'sql_name' => 'problem_id'
  710 		  },
  711 		  'max_attempts' => {
  712 			'is_keyfield' => '',
  713 			'sql_type' => 'INT',
  714 			'sql_name' => 'max_attempts'
  715 		  },
  716 		  'value' => {
  717 			'is_keyfield' => '',
  718 			'sql_type' => 'INT',
  719 			'sql_name' => 'value'
  720 		  },
  721 		  'source_file' => {
  722 			'is_keyfield' => '',
  723 			'sql_type' => 'TEXT',
  724 			'sql_name' => 'source_file'
  725 		  },
  726 		  'set_id' => {
  727 			'is_keyfield' => 1,
  728 			'sql_type' => 'BLOB',
  729 			'sql_name' => 'set_id'
  730 		  }
  731 		},
  732 		'keyfield_order' => [
  733 		  'set_id',
  734 		  'problem_id'
  735 		],
  736 		'field_order' => [
  737 		  'set_id',
  738 		  'problem_id',
  739 		  'source_file',
  740 		  'value',
  741 		  'max_attempts'
  742 		],
  743 		'sql_name' => '6SC36NukknC3IT3M_problem'
  744 	  },
  745 	  'user' => {
  746 		'fields' => {
  747 		  'email_address' => {
  748 			'is_keyfield' => '',
  749 			'sql_type' => 'TEXT',
  750 			'sql_name' => 'email_address'
  751 		  },
  752 		  'student_id' => {
  753 			'is_keyfield' => '',
  754 			'sql_type' => 'TEXT',
  755 			'sql_name' => 'student_id'
  756 		  },
  757 		  'comment' => {
  758 			'is_keyfield' => '',
  759 			'sql_type' => 'TEXT',
  760 			'sql_name' => 'comment'
  761 		  },
  762 		  'status' => {
  763 			'is_keyfield' => '',
  764 			'sql_type' => 'TEXT',
  765 			'sql_name' => 'status'
  766 		  },
  767 		  'recitation' => {
  768 			'is_keyfield' => '',
  769 			'sql_type' => 'TEXT',
  770 			'sql_name' => 'recitation'
  771 		  },
  772 		  'section' => {
  773 			'is_keyfield' => '',
  774 			'sql_type' => 'TEXT',
  775 			'sql_name' => 'section'
  776 		  },
  777 		  'user_id' => {
  778 			'is_keyfield' => 1,
  779 			'sql_type' => 'BLOB',
  780 			'sql_name' => 'user_id'
  781 		  },
  782 		  'last_name' => {
  783 			'is_keyfield' => '',
  784 			'sql_type' => 'TEXT',
  785 			'sql_name' => 'last_name'
  786 		  },
  787 		  'first_name' => {
  788 			'is_keyfield' => '',
  789 			'sql_type' => 'TEXT',
  790 			'sql_name' => 'first_name'
  791 		  }
  792 		},
  793 		'keyfield_order' => [
  794 		  'user_id'
  795 		],
  796 		'field_order' => [
  797 		  'user_id',
  798 		  'first_name',
  799 		  'last_name',
  800 		  'email_address',
  801 		  'student_id',
  802 		  'status',
  803 		  'section',
  804 		  'recitation',
  805 		  'comment'
  806 		],
  807 		'sql_name' => '6SC36NukknC3IT3M_user'
  808 	  },
  809 	  'set_user' => {
  810 		'fields' => {
  811 		  'version_time_limit' => {
  812 			'is_keyfield' => '',
  813 			'sql_type' => 'INT',
  814 			'sql_name' => 'version_time_limit'
  815 		  },
  816 		  'set_header' => {
  817 			'is_keyfield' => '',
  818 			'sql_type' => 'TEXT',
  819 			'sql_name' => 'set_header'
  820 		  },
  821 		  'psvn' => {
  822 			'is_keyfield' => '',
  823 			'sql_type' => 'INT NOT NULL PRIMARY KEY AUTO_INCREMENT',
  824 			'sql_name' => 'psvn'
  825 		  },
  826 		  'hardcopy_header' => {
  827 			'is_keyfield' => '',
  828 			'sql_type' => 'TEXT',
  829 			'sql_name' => 'hardcopy_header'
  830 		  },
  831 		  'version_creation_time' => {
  832 			'is_keyfield' => '',
  833 			'sql_type' => 'BIGINT',
  834 			'sql_name' => 'version_creation_time'
  835 		  },
  836 		  'open_date' => {
  837 			'is_keyfield' => '',
  838 			'sql_type' => 'BIGINT',
  839 			'sql_name' => 'open_date'
  840 		  },
  841 		  'problem_randorder' => {
  842 			'is_keyfield' => '',
  843 			'sql_type' => 'INT',
  844 			'sql_name' => 'problem_randorder'
  845 		  },
  846 		  'versions_per_interval' => {
  847 			'is_keyfield' => '',
  848 			'sql_type' => 'INT',
  849 			'sql_name' => 'versions_per_interval'
  850 		  },
  851 		  'version_last_attempt_time' => {
  852 			'is_keyfield' => '',
  853 			'sql_type' => 'BIGINT',
  854 			'sql_name' => 'version_last_attempt_time'
  855 		  },
  856 		  'time_interval' => {
  857 			'is_keyfield' => '',
  858 			'sql_type' => 'INT',
  859 			'sql_name' => 'time_interval'
  860 		  },
  861 		  'set_id' => {
  862 			'is_keyfield' => 1,
  863 			'sql_type' => 'BLOB',
  864 			'sql_name' => 'set_id'
  865 		  },
  866 		  'published' => {
  867 			'is_keyfield' => '',
  868 			'sql_type' => 'INT',
  869 			'sql_name' => 'published'
  870 		  },
  871 		  'assignment_type' => {
  872 			'is_keyfield' => '',
  873 			'sql_type' => 'TEXT',
  874 			'sql_name' => 'assignment_type'
  875 		  },
  876 		  'due_date' => {
  877 			'is_keyfield' => '',
  878 			'sql_type' => 'BIGINT',
  879 			'sql_name' => 'due_date'
  880 		  },
  881 		  'answer_date' => {
  882 			'is_keyfield' => '',
  883 			'sql_type' => 'BIGINT',
  884 			'sql_name' => 'answer_date'
  885 		  },
  886 		  'user_id' => {
  887 			'is_keyfield' => 1,
  888 			'sql_type' => 'BLOB',
  889 			'sql_name' => 'user_id'
  890 		  },
  891 		  'attempts_per_version' => {
  892 			'is_keyfield' => '',
  893 			'sql_type' => 'INT',
  894 			'sql_name' => 'attempts_per_version'
  895 		  }
  896 		},
  897 		'keyfield_order' => [
  898 		  'user_id',
  899 		  'set_id'
  900 		],
  901 		'field_order' => [
  902 		  'user_id',
  903 		  'set_id',
  904 		  'psvn',
  905 		  'set_header',
  906 		  'hardcopy_header',
  907 		  'open_date',
  908 		  'due_date',
  909 		  'answer_date',
  910 		  'published',
  911 		  'assignment_type',
  912 		  'attempts_per_version',
  913 		  'time_interval',
  914 		  'versions_per_interval',
  915 		  'version_time_limit',
  916 		  'version_creation_time',
  917 		  'problem_randorder',
  918 		  'version_last_attempt_time'
  919 		],
  920 		'sql_name' => '6SC36NukknC3IT3M_set_user'
  921 	  },
  922 	  'set' => {
  923 		'fields' => {
  924 		  'version_last_attempt_time' => {
  925 			'is_keyfield' => '',
  926 			'sql_type' => 'BIGINT',
  927 			'sql_name' => 'version_last_attempt_time'
  928 		  },
  929 		  'version_time_limit' => {
  930 			'is_keyfield' => '',
  931 			'sql_type' => 'INT',
  932 			'sql_name' => 'version_time_limit'
  933 		  },
  934 		  'versions_per_interval' => {
  935 			'is_keyfield' => '',
  936 			'sql_type' => 'INT',
  937 			'sql_name' => 'versions_per_interval'
  938 		  },
  939 		  'time_interval' => {
  940 			'is_keyfield' => '',
  941 			'sql_type' => 'INT',
  942 			'sql_name' => 'time_interval'
  943 		  },
  944 		  'set_header' => {
  945 			'is_keyfield' => '',
  946 			'sql_type' => 'TEXT',
  947 			'sql_name' => 'set_header'
  948 		  },
  949 		  'set_id' => {
  950 			'is_keyfield' => 1,
  951 			'sql_type' => 'BLOB',
  952 			'sql_name' => 'set_id'
  953 		  },
  954 		  'hardcopy_header' => {
  955 			'is_keyfield' => '',
  956 			'sql_type' => 'TEXT',
  957 			'sql_name' => 'hardcopy_header'
  958 		  },
  959 		  'published' => {
  960 			'is_keyfield' => '',
  961 			'sql_type' => 'INT',
  962 			'sql_name' => 'published'
  963 		  },
  964 		  'version_creation_time' => {
  965 			'is_keyfield' => '',
  966 			'sql_type' => 'BIGINT',
  967 			'sql_name' => 'version_creation_time'
  968 		  },
  969 		  'due_date' => {
  970 			'is_keyfield' => '',
  971 			'sql_type' => 'BIGINT',
  972 			'sql_name' => 'due_date'
  973 		  },
  974 		  'assignment_type' => {
  975 			'is_keyfield' => '',
  976 			'sql_type' => 'TEXT',
  977 			'sql_name' => 'assignment_type'
  978 		  },
  979 		  'open_date' => {
  980 			'is_keyfield' => '',
  981 			'sql_type' => 'BIGINT',
  982 			'sql_name' => 'open_date'
  983 		  },
  984 		  'answer_date' => {
  985 			'is_keyfield' => '',
  986 			'sql_type' => 'BIGINT',
  987 			'sql_name' => 'answer_date'
  988 		  },
  989 		  'attempts_per_version' => {
  990 			'is_keyfield' => '',
  991 			'sql_type' => 'INT',
  992 			'sql_name' => 'attempts_per_version'
  993 		  },
  994 		  'problem_randorder' => {
  995 			'is_keyfield' => '',
  996 			'sql_type' => 'INT',
  997 			'sql_name' => 'problem_randorder'
  998 		  }
  999 		},
 1000 		'keyfield_order' => [
 1001 		  'set_id'
 1002 		],
 1003 		'field_order' => [
 1004 		  'set_id',
 1005 		  'set_header',
 1006 		  'hardcopy_header',
 1007 		  'open_date',
 1008 		  'due_date',
 1009 		  'answer_date',
 1010 		  'published',
 1011 		  'assignment_type',
 1012 		  'attempts_per_version',
 1013 		  'time_interval',
 1014 		  'versions_per_interval',
 1015 		  'version_time_limit',
 1016 		  'version_creation_time',
 1017 		  'problem_randorder',
 1018 		  'version_last_attempt_time'
 1019 		],
 1020 		'sql_name' => '6SC36NukknC3IT3M_set'
 1021 	  }
 1022 	);
 1023 }

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9