MySQL & mSQL

Msql.pm


Модуль Msql.pm является изначальным интерфейсом Perl к mSQL. Хотя его заменили модули DBI, все же осталось много сайтов, основанных на этом старом интерфейсе. Чтобы продемонстрировать использование Msql.pm, мы продолжим работу с нашим примером «помощника учителя».

Поскольку нам требуется определить школьные классы, в которых будут проводиться экзамены, рассмотрим таблицу предметов. Ее структура выглядит так:

CREATE TABLE subject (

id INT NOT NULL,

name CHAR(500),

teacher CHAR(100) )

CREATE UNIQUE INDEX idxl ON subject (

id,

name,

teacher

)

CREATE SEQUENCE ON subject

Число id является уникальным идентификатором школьного класса, а поля name и teacher являются наименованием курса и фамилией преподавателя соответственно. Все три поля проиндексированы, что ускоряет выполнение запросов. И наконец, мы определили последовательность для таблицы. Эта последовательность генерирует ID.



CGI-программа для обработки этих данных должна выполнять несколько функций:

  • Находить предмет в базе данных.

  • Выводить найденный предмет.

  • Добавлять в базу данных новый предмет.

  • Изменять параметры предмета в базе данных.

    Используя мощь Perl и mSQL, можно без труда объединить все эти функции в одном файле, subject.cgi. Для каждой из операций мы создадим свою функцию. Центральной частью программы будет своего рода коммутатор, распределяющий поступающие запросы по соответствующим функциям. Сами операции мы опишем позднее.

    #Выбор нужной части сценария осуществляется в зависимости

    # от параметра 'action'.

    # Если 'action' не задан, вызывается функция defaultQ.

    &default if not param('action');

    # Этот прием из Camel 2 имитирует переключатель 'switch' в языке С. foreach[A04] (param('action')) {

    /view/ and do { Sview; last; };

    /add$/ and do { &add; last; };

    /add2/ and do { Sadd2; last; };

    /add3/ and do { &add3; last; };

    /add4/ and do { &add4; last; };

    /schange$/ and do { &schange; last; };

    /schange2/ and do { &schange2; last; };


    /lchange$/ and do { &lchange; last; };

    /Ichange2/ and do { &lchange2; last; };

    /IchangeS/ and do { &lchange3; last; };

    /delete/ and do { Sdelete; last; };

    &default; }

    Пункты «add», «schange» и «Ichange» должны иметь завершающий «$», чтобы не быть спутанными со сходными. Если бы не было «$», то «add» соответствовал бы также add2, add3 и add4. Альтернативный прием - поместить «add», «schange» и «Ichange» после всех остальных функций, чтобы вызываться при отсутствии совпадений с предыдущими пунктами. Однако если впоследствии добавятся другие пункты, могут возникнуть ошибки. Третий способ - полностью устранить неоднозначность, используя /^view$/, /*add$/ и т. д. При этом придется ввести несколько больше символов, но возможность ошибки будет полностью устранена.

    Остается лишь проработать детали, реализовав каждую функцию.

    Функция default выводит исходную форму, которую видит пользователь, позволяющую ему выбрать тип операции. Эта функция вызывается, если CGI-программа вызывается без параметров, например, как http://www.myserver.com/teach/subject.cgi, или если параметр ACTION не соответствует ни одной из существующих функций. Можно было бы также создать функцию, выводящую сообщение об ошибке, если параметр ACTION неизвестен.

    sub default {

    print header, start_html('title'=>'Subjects', 'BGCOLOR'=>'white'):

    print «END_OF_HTML; <h1>Предметы</h1>

    <р>Выберите операцию и предмет (если это допустимо).

    <FORM ACTION="subject.cgi" METHOD=POST>

    <p>

    <SELECT NAME="action">

    <OPTION VALUE="view">npocмотp предмета

    <OPTION VALUE="аdd">Добавление предмета

    <OPTION VALUE="schange">Изменение предмета

    <OPTION VALUE="lchange" SELECTED>Изменить список классов

    <OPTION VALUE="delete">Удалить предмет </select>



    END_OF_HTML

    # См. ниже 'sub print_subjects'.

    &print_subjects;

    print «END_OF_HTML;

    <P>

    <INPUT TYPE=SUBMIT VALUE="Выполнить операцию">

    <INPUT TYPE=RESET>

    </form></body></html>

    HTML

    }

    Основных операций пять: «view» (просмотр), «add» (добавление), «schange» (изменение данных о предмете), «Ichange» ( изменить список классов по предмету) и «delete» (удалить). Например, мы подробно рассмотрим операцию «add». Она разбита на четыре отдельные функции, потому что диалог с пользователем приходится проводить до четырех раз. Для передачи данных от одной формы к другой используются скрытые переменные, пока, в конце концов, не будет создан класс.

    Первая функция порождает форму для ввода начальных сведений о классе, в том числе его названия, имени учителя и количества учащихся.

    sub add {

    my (%fields);

    foreach ('name','size','teacher') {

    if (param($_)) { $fields{$_} = param($_); } else { $fields{$_} = ""; } }

    print header, start_html('title'=>'Add a Subject','BGCOLOR'=>'white');

    print «END_OF_HTML; <H1>Add a Subject</h1> <form METHOD=POST ACTION="subject.cgi"> <P>

    Название предмета:

    <input size=40 name="name" value="$fields{'name'}"><br>

    Фамилия учителя:

    <input size=40 name="teacher" value="$fields{'teacher'}"><br>

    Число учащихся в классе:

    <input size=5 name="size" value="$fields{'size'}">

    <P>

    <INPUT TYPE=HIDDEN NAME="action" VALUE="add2">

    <INPUT TYPE=SUBMIT VALUE="Следующая страница ">

    <INPUT TYPE=RESET>

    </form>

    <P>

    <A HREF="subject.cgi">Перейти</a> назад к главной странице предметов.<br>

    <А HREF=". ">Перейти</а> к домашней странице Помощи учителю.



    </body></html>

    END_OF_HTHL

    }

    Функция проверяет, не имеют ли какие-либо поля предустановленные значения. Это придает функции дополнительную гибкость, позволяя использовать ее как шаблон для классов со значениями по умолчанию, возможно, генерируемыми какой-либо другой CGI-программой.

    Значения, полученные в первой части процесса добавления, передаются обратно CGI-программе для использования в функции add2. Функция add2 сначала проверяет, существует ли уже класс. Если существует, то пользователю посылается сообщение об ошибке, и он может изменить название класса.

    Если класс еще не существует, функция проверяет, какое число учащихся введено. Если никакое, то класс создается без учащихся, их можно добавить позднее. Если нее число задано, создается класс и выводится форма, в которую можно ввести данные о каждом учащемся.

    sub add2 {

    my $name = param('name');

    # Нам нужна копия имени, которая кодируется для URL.

    my $enc_name = &cgi_encode($name);

    # Нам также нужна копия имени, которую можно спокойно цитировать для

    # ввода в базу. Msql использует с этой целью функцию Msql::quote().

    my $query_name = $dbh->quote($name);

    # Строим запрос для проверки существования предмета,

    my $query ="select id, name, teacher from subject where name=$query_name";

    #Если пользователь ввел фамилию учителя, отдельно проверяем фамилию,

    # поскольку могут быть два курса с одинаковым названием, но

    # разными учителями.

    if (param('teacher')) {

    $teacher = param('teacher');

    $enc_teacher = &cgi_encode($teacher);

    my $query_teacher = $dbh->quote($teacher);

    $query .= " and teacher=$query_teacher"; }

    # Теперь посылаем запрос серверу mSQL

    my $out = $dbh->query($query);

    ft Проверяем значение $out->numrows, чтобы узнать, были ли возвращены

    # какие-либо строки. Если были, и пользователь не задал параметр 'override'

    # (переопределить), то мы выходим с сообщением, что класс уже

    # существует, и давая пользователю возможность все-таки ввести класс



    # (повторно передав форму с установленным параметром 'override',

    if ($out->numrows and not param('override')) { # Печать страницы 'Класс уже существует'.

    } else {

    # Теперь вводим данные в базу.

    # Сначала нужно выбрать новое число из

    # последовательности для таблицы.

    $out = $dbh->query("select _seq from subject");

    my ($id) = $out->fetchrow;

    # Теперь вводим информацию в базу данных, используя

    # полученное из последовательности число в качестве ID.

    $query = "INSERT INTO subject (id, name, teacher)

    VALUES ($id, '$name', 'Steacher')"; $dbh->query($query);

    # Если пользователь не задал размер класса, выходим

    # с сообщением о том, что пользователь может добавить

    # число учащихся позже, if (not param('size')) {

    # Вывод страницы с сообщением об успехе.

    } else { \

    # Теперь выводим форму, позволяющую пользователю

    # ввести имена всех учащихся в классе.

    print header, start_html('title'=>'Create Class List',

    'BGCOLOR'=>'white'); print <<END_OF_HTML;

    <Н1>Создать список класса</h1> <P>

    <B>$name</b>

    добавлен в базу данных. Теперь можно ввести фамилии учащихся в этом классе. Добавить или исключить учащихся можно позднее с главной страницы предметов

    <а href="subject.cgi"> </a>.

    <Р>

    <FORM METHOD=POST ACTION="subject.cgi">

    <INPUT TYPE=HIDDEN NAME="action" VALUE="add3">

    <INPUT TYPE=HIDDEN NAME="id" VALUE="$id">

    <table>

    <ТР><ТН><ТН>Имя<ТН>Отчество/Инициал

    <ТН>Фамилия<ТН>мл.,ст.,III,и т.д.

    </tr>

    END_OF_'HTML

    for $i (1.,$size) {

    print <<END_OF_HTML;

    <TR><TD>$i<TD><INPUT SIZE=15 NAME="first$i"><TD>

    <lNPUT SIZE=15 NAME="middle$i">

    <TD><INPUT SIZE=15 NAME="last$i"><TD>

    <INPUT SIZE=5 NAME="ext$i"></tr>



    END_OF_HTML

    }

    print <<END_OF_HTML; </table>

    <INPUT TYPE=SUBMIT VALUE=" Ввести список класса ">

    <INPUT TYPE=RESET> </form>

    </body></html>

    END_OF_HTML

    } } }

    Обратите внимание, что функция использовала три копии параметра name. Для использования в составе URL все специальные символы должны быть заменены особым образом. Для этого в коде примера используется функция cgi_encode . Кроме того, чтобы ввести строку в базу данных mSQL, вместо некоторых символов нужно использовать управляющие символы. Интерфейс MsqlPerl предоставляет для этого функцию quote, доступную через любой описатель базы данных. Наконец, при выводе на экран используется непреобразованный вариант переменной.

    При добавлении класса в базу данных удобно использовать такую функцию mSQL, как последовательности. Вспомним, что в таблице class была определена последовательность. Ее значения используются в качестве уникального идентификатора для каждого класса. Благодаря этому два класса могут иметь одинаковые названия (или одного и того же учителя и т. д.) и все же быть различными. Это также окажется удобным при дальнейших изменениях в классе. Пока между формами передается уникальный ID, все прочие данные о классе можно свободно изменять.

    Наконец, отметим, что выводимая этой функцией форма для ввода учащихся генерируется динамически. Для вывода формы с правильным числом записей используется введенное раньше число учащихся. Помните, что CGI-программа полностью управляет генерируемым HTML. Любая часть, включая формы, может быть создана программным образом.

    Если пользователь не ввел никаких учащихся, то работа на этом закончена. Позднее для добавления учащихся можно воспользоваться функцией модификации. Однако если требуется ввести учащихся, данные о них передаются функции add3, как показано ниже:

    sub add3 {

    if (not param('id')) { &end("Требуется числовой ID"); }

    my $id = param( 'id');

    my ©list = &find_last_student;



    my ($ref_students,$ref_notstudents) = &find_matching_students(@list);

    @students = @$ref_students

    if $ref_students;

    @notstudents = @$ref_notstudents

    if $ref_notstudents;

    if (@notstudents) {

    # Вывести форму, говорящую пользователю, что в списке

    # есть несуществующие учащиеся. Пользователь может автоматически

    # создать учащихся или вернуться и исправить опечатки.

    } else {

    &update_students($id,@students);

    #Вывести форму успешного завершения работы.

    } }

    В этой функции основная часть работы выполняется другими функциями. Это обусловлено тем, что в других частях CGI-программы возникают сходные задачи, которые полезно решать с помощью совместно используемых функций. Первая такая функция - f ind_last_student , которая изучает данные формы и возвращает список имеющихся в форме номеров, не связанных с ID в базе данных, всех введенных пользователем учащихся. Это необходимо, поскольку, как упоминалось раньше, предыдущая форма генерируется динамически и нет возможности непосредственно узнать, сколько учащихся включено.

    sub find_last_student {

    my @params = param; my @list = (); foreach (@params) {

    next if not param($_);

    # Исключить все 'пустые' поля

    if (/-(first|middle|last|ext)(\d+)/) {

    my $num = $2;

    if (not grep(/"$num$/,@list)) { push(@list,$num); } } }

    @list = sort { $a <=> $b} @list; return @list;

    }

    Обратите внимание, что функция возвращает все числа, а не только последнее, которое, предположительно, будет числом введенных учащихся. Хотя предыдущая форма вывела число запрошенных пользователем записей, нет гарантии, что пользователь заполнил их все. Он мог пропустить строку, которая не будет включена в данные формы. Поэтому необходимо выявить все введенные числа. Результаты работы этой функции пересылаются следующей вспомогательной функции find_matching_students , как показано ниже:

    sub find_matching_students { my §list = @_;

    my ($i,@students,@notstudents); §students = ();

    @notstudents = ();

    if (@list) {



    foreach $i (@list) {

    my @query = ();

    # Строим запрос, который ищет заданного учащегося,

    my $query = "select id, subjects from student where ";

    foreach ('first','middle','last', 'ext') {

    if (param("$_$i")) {

    my $temp = param("$_$i");

    # В mSQL и MySQL одиночные кавычки служат ограничителями

    # имен полей, и им должен предшествовать

    # управляющий символ "\",

    # который управляет и сам собой,

    # чтобы быть введенным буквально.

    $temp =~ s/7\\'/g;

    push(@query, "$_ = '$temp'"); } }

    $query = join(" and ",§query);

    # Посылаем запрос базе данных.

    my $out = $dbh->query($query);

    # Если база данных ничего не возвращает, добавляем

    # учащегося к массиву @notstudents.

    if (not $out->numrows) {

    push(@notstudents, [ param("first$i"), param("middle$i"), param("last$i"), param("ext$i") ]);

    # В противном случае добавляем студента в массив ©students.

    } else {

    my ($id,$subjects) = $out->fetchrow;

    push(@students,[$id,$subjects]); } } }

    return(\§students,\@notstudents); }

    Эта функция пробегает по всем заданным именам учащихся и проверяет, есть ли уже они в базе данных. Если они существуют, данные о них записываются в массив с именем ©students , в противном случае - в массив @notstudents . Данные о каждом учащемся хранятся в безымянном массиве, создавая своего рода объект учащегося. В итоге функция возвращает ссылки на оба массива. Она не может возвратить данные как обычный массив, поскольку будет невозможно определить, где закончился один массив и начался другой.

    И последняя вспомогательная функция - update_students , которая добавляет класс к списку классов для каждого существующего учащегося.

    sub update_students {

    my $id = shift;

    my ©students = @_;

    foreach (©students) {

    my($sid, $subjects)=©$_;

    if (not Ssubjects) { Ssubjects = ":$id:"; }

    elsif (Ssubjects !" /:$id:/)

    { Ssubjects .= "$id:"; }



    my $query = "update sti/dent set subjects='Ssubjects'

    where id=$id";

    $dbh->query($query); } }

    Эта функция осуществляет запрос к таблице student, совершенно независимой от таблицы subject. В пределах одной CGI-программы можно работать с любым числом различных таблиц одной базы данных. Можно переключаться с одной базы данных на другую, но одновременно может быть активна только одна база данных. Эта функция извлекает список предметов для каждого учащегося и добавляет к нему новый предмет, если его еще нет в списке.

    Функция обрабатывает все возможные случаи, кроме одного, когда к предмету приписаны учащиеся, которых еще нет в таблице student. В этом случае список новых учащихся передается функции add4, как показано ниже:

    sub add4 {

    #получить список ©students и @notstudents

    &update_students($id,@students) if @students;

    &insert_students($id,@notstudents) if @notstudents;

    # Вывести страницу успешного завершения. }

    Эта функция разделяет список учащихся на существующих и несуществующих тем же способом, что и add3. Затем она обновляет список существующих учащихся с помощью функции update_students , показанной раньше. Несуществующие учащиеся посылаются новой вспомогательной функции insert_students :

    sub insert_students { foreach $i (@list) {

    # Производится выбор очередного числа из последовательности,

    # определенной в таблице. Зто число используется как ID учащегося,

    my $out = $dbh->query('select _seq from student');

    my($sid) = $out->fetchrow;

    # Для включения в базу данных все строки

    # нужно процитировать.

    my ($first, $middle, $last, $ext) = (

    $dbh->quote(param("first$i")),

    $dbh->quote(param("middle$i")),

    $dbh->quote(param("last$i")),

    $dbh->quote(param("ext$i")) );

    my $query = "insert into student (id, first, middle, last,

    ext, subjects) VALUES ($sid, $first, $middle,

    $last, $ext, ':$id:')";

    $dbh->query($query); } }

    И эта функция обращается к таблице student, а не subject. Из последовательности, определенной в таблице student, извлекаются ID для новых учащихся, затем учащиеся вводятся в таблицу с этими ID.




    Содержание раздела