Parent Directory
|
Revision Log
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 |