Book HomeMySQL and mSQLSearch this book

10.4. MysqlPerl

Monty Widenius, the author of MySQL, also wrote the Perl interface to MySQL, Mysql.pm. This was based on the mSQL module, Msql.pm. Thus, the interfaces of the two modules are almost identical. In fact, we recently converted an entire site from mSQL to MySQL by running "perl -e 's/^Msql/Mysql/" *.cgi" in every directory containing a CGI. This covers 95% of the work involved. Of course, this does not give you any of the advantages of MySQL, but it is a quick and easy way to start down the road to MySQL. Mysql.pm is maintained as part of msql-mysql-modules by Jochen Wiedmann.

NOTE

One of the largest differences between MySQL and mSQL is the way they handle sequences. In mSQL, a sequence is defined on a table with a command like CREATE SEQUENCE on tablename. The sequence value is then read as if it were a normal table value with the command SELECT _seq from tablename. MySQL adds the flag AUTO_INCREMENT to the primary key. Whenever a null value is inserted into this field, it is automatically incremented. Both MySQL and mSQL allow only one sequence per table. For a full discussion on sequences in MySQL and mSQL, see Chapter 6, "SQL According to MySQL and mSQL".

As an example of some of the features of Mysql.pm, let's go back to the tests example. Now that we have subject.cgi taken care of, the next step is the table of student information. The structure of the students table is as follows:

CREATE TABLE student (
  id INT NOT NULL auto_increment,
  first VARCHAR(50),
  middle VARCHAR(50),
  last VARCHAR(50),
  ext VARCHAR(50),
  subjects VARCHAR(100),
  age INT,
  sex INT,
  address BLOB,
  city VARCHAR(50),
  state VARCHAR(5),
  zip VARCHAR(10),
  phone VARCHAR(10),
  PRIMARY KEY (id)
)

All of the information used by the subject.cgi program is in this table, as well as other information pertaining to the student. The program that handles this table, student.cgi must perform all of the functions that subject.cgi did for the subject table.

NOTE

It is not possible to access a mSQL database with the Mysql.pm module, or MySQL with Msql.pm. The student.cgi program expects to find a MySQL version of the subjects table. Likewise, the subject.cgi program expects an mSQL version of the students table.

To illustrate the operation of Mysql.pm, we will examine in detail the portion of student.cgi that enables a user to change the information about a student. Just like the "add" action in the Msql.pm example was broken up into four separate functions, the "change" action here is separated into three individual functions.

The first function, change, prints out a form that allows the user to search for a student to change, as shown in the following:

sub change {
   print header, start_html('title'=>'Student Change Search',
      'BGCOLOR'=>'white');
   &print_form('search2','Search for a Student to Change',1);
   print <<END_OF_HTML;
<p>
<INPUT TYPE=HIDDEN NAME="subaction" VALUE="change2">
<INPUT TYPE=SUBMIT VALUE=" Search for Students ">
 <INPUT TYPE=SUBMIT NAME="all" VALUE=" View all Students ">
 <INPUT TYPE=RESET>
</form></body></html>
END_OF_HTML
}

The form used for searching for a student to "change" is so similar to the form used to searching for a student to "view" and the one to "add" a student that a single function, print_form, is used for all three purposes, as shown in the following:

sub print_form {
   my ($action,$message,$any) = @_;

   print <<END_OF_HTML;
<FORM METHOD=post ACTION="students.cgi">
<INPUT TYPE=HIDDEN NAME="action" VALUE="$action">
<H1>$message</h1>
END_OF_HTML
   if ($any) {
      print <<END_OF_HTML;
<p>Search for <SELECT NAME="bool">
<OPTION VALUE="or">any
<OPTION VALUE="and">all
</select> of your choices.
END_OF_HTML
   }
   print <<END_OF_HTML;
<p>
First: <INPUT NAME="first" SIZE=20>
 Middle: <INPUT NAME="middle" SIZE=10>
 Last: <INPUT NAME="last" SIZE=20>
 Jr./III/etc.: <INPUT NAME="ext" SIZE=5>
<br>
Address: <INPUT NAME="address" SIZE=40><br>
City: <INPUT NAME="city" SIZE=20> 
 State: <INPUT NAME="state" SIZE=5>
 ZIP: <INPUT NAME="zip" SIZE=10><br>
Phone: <INPUT NAME="phone" SIZE=15><br>
Age: <INPUT NAME="age" SIZE=5> Sex: <SELECT NAME="sex">
END_OF_HTML
   if ($any) {
      print <<END_OF_HTML;
<OPTION VALUE="">Doesn't Matter
END_OF_HTML
   }
   print <<END_OF_HTML;
<OPTION VALUE="1">Male
<OPTION VALUE="2">Female
</select><br>
<p>
Enrolled in:<br>
END_OF_HTML
   &print_subjects("MULTIPLE SIZE=5");

}

By using three parameters, this function customizes a form template so that it can be used for several very different purposes. Notice that this helper function calls another helper function, print_subjects. This function queries the subject table as seen in the Msql.pm example and prints out a list of all of the available subjects.

sub print_subjects {
   my $modifier = "";
   $modifier = shift if @_;
   print qq%<SELECT NAME="subjects" $modifier>\n%;
   my $out = $dbh->query("select * from subject order by name");
   while(my(%keys)=$out->fetchhash) {
      print qq%<OPTION VALUE="$keys{'id'}">$keys{'name'}\n%;
   }
   print "</select>\n";
}

The search parameters entered in this first form are then sent to the search2 function, which actually performs the search. This is actually the function written to search for a student to view. Since its function is exactly what we need, we can piggy-back off of it as long as we tell it that we want to go to the next change function, change2, after the search. That is why we have the hidden variable subaction=change2 in the form. It tells search2, as shown in the following, where to send the user next:

sub search2 {
   my $out = $dbh->query(&make_search_query);
   my $hits = $out->numrows;
   my $subaction = "view";
   $subaction = param('subaction') if param('subaction');
   print header, start_html('title'=>'Student Search Result',
      'BGCOLOR'=>'white');

   if (not $hits) {
      print <<END_OF_HTML;
<H1>No students found</h1>
<p>
No students matched your criteria.
END_OF_HTML
   } else {
      print <<END_OF_HTML;
<H1>$hits students found</h1>
<p>
<UL>
END_OF_HTML
      while(my(%fields)=$out->fetchhash) {
         print qq%<LI>
<A HREF="students.cgi?action=$subaction&id=$fields{'id'}">$fields{'first'}
 $fields{'middle'} $fields{'last'}%;
         print ", $fields{'ext'}" if $fields{'ext'};
         print "\n</a>";
      }
   }
   print <<END_OF_HTML;
</ul>
<p>
<A HREF="students.cgi?action=search">Search</a> again.
</body></html>
END_OF_HTML
}

With help from the make_search_query function, this function first searches for students that match the search term. It then displays a list of the matches from which the user can select. The ID number of the selected entry is then sent to the change2 function, as shown in the following:

sub change2 {
      my $out = $dbh->query("select * from student where id=$id");
        
      my($did,$first,$middle,$last,$ext,$subjects,$age,$sex,$address,
         $city,$state,$zip,$phone) = $out->fetchrow;

      my @subjects = split(/:/,$subjects);
      shift @subjects;
      my $name = "$first $middle $last";
      if ($ext) { $name .= ", $ext"; }

      print header, start_html('title'=>"$name",'BGCOLOR'=>'white');
      print <<END_OF_HTML;
<H1>$name</h1>
<p>
<FORM ACTION="students.cgi" METHOD=POST>
<INPUT TYPE=HIDDEN NAME="action" VALUE="change3">
<INPUT TYPE=HIDDEN NAME="id" VALUE="$id">
First: <INPUT NAME="first" VALUE="$first" SIZE=20>
Middle: <INPUT NAME="middle" VALUE="$middle" SIZE=10>
Last: <INPUT NAME="last" VALUE="$last" SIZE=20>
Jr./III/etc.: <INPUT NAME="ext" VALUE="$ext" SIZE=5>
<br>
Address: <INPUT NAME="address" VALUE="$address" SIZE=40><br>
City: <INPUT NAME="city" VALUE="$city" SIZE=20> 
State: <INPUT NAME="state" VALUE="$state" SIZE=5>
ZIP: <INPUT NAME="zip" VALUE="$zip" SIZE=10><br>
Phone: <INPUT NAME="phone" VALUE="$phone" SIZE=15><br>
Age: <INPUT NAME="age" VALUE="$age" SIZE=5> Sex: 
END_OF_HTML
      my %sexes = ( '1' => 'Male',
         '2' => 'Female'
      );
      print popup_menu('name'=>'sex',
         'values'=>['1','2'],
         'default'=>"$sex",
         'labels'=>\%sexes);
      print <<END_OF_HTML;
<p>
Enrolled in:<br>
END_OF_HTML
      my @ids = ();
      my %subjects = ();
      my $out2 = $dbh->query("select id,name from subject order by name");
      while(my($id,$subject)=$out2->fetchrow) { 
         push(@ids,$id); 
         $subjects{"$id"} = $subject;
      }
      print scrolling_list('name'=>'subjects',
         'values'=>[@ids],
         'default'=>[@subjects],
         'size'=>5,
         'multiple'=>'true',
         'labels'=>\%subjects);
      print <<END_OF_HTML;
<p>
<INPUT TYPE=SUBMIT VALUE=" Change Student ">
 <INPUT TYPE=SUBMIT NAME="delete" VALUE=" Delete Student ">
 <INPUT TYPE=RESET>
</form></body></html>
END_OF_HTML
}

The primary purpose of this function is to print out a form very similar to the one generated from print_from. However, the values of this form must have the values of the chosen student preinserted as default values. This way, the user can edit whichever fields of the student he or she wishes without changing the rest.

A couple of functions provided by the CGI.pm module come in very handy when printing form with default values. Most importantly, the function CGI::scrolling_list prints out an HTML <SELECT> block with the parameters you provide. Among other parameters, the function takes the parameters values, default, and labels which are references to the values of each <OPTION> tag, the ones which should be preselected and the labels that user sees respectively.

The output of this function is a complete set of information, just as if it were coming from an add form. The difference is that the data is for a student which already exists in the database. The change3 function accepts this data and updates the student, as shown in the following:

sub change3 {
   if (param('delete')) { &delete2($id); }
   else {
      my $query = "update student set ";
      my @query = ();
      foreach ('first', 'middle', 'last', 'ext', 'address', 'city',
         'state', 'zip', 'phone') {
         if (param($_)) { push(@query,"$_ = ". 
            $dbh->quote(param($_))); 
         }
      }
      push(@query,"age=".param('age')) if param('age');
      push(@query,"sex=".param('sex')) if param('sex');
        
      my $subjects = "':";
      $subjects .= join(":",param('subjects'));
      $subjects .= ":" unless $subjects eq "':";
      $subjects .= "'";
      push(@query,"subjects=$subjects");

      $query .= join(", ",@query) . " where id=$id";
      $dbh->query($query);

      print header, start_html('title'=>'Student Changed',
         'BGCOLOR'=>'white');
      # Print success form
      ...   
   }
}

Note that if the user chose the "Delete" button on the change page, this function automatically passes the ball to the delete function. This is one major advantage of integrating several functions into one program. If no user interaction is required, you can skip from function to function without sending redirect messages to the user.

The rest of this function is fairly straightforward. The information about the student is gathered into an UPDATE query, which is sent to the MySQL server. A success page is then sent to the user.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.