Типы данных столбцов

Определение внешнего ключа при создании таблицы Delivery:

CREATE TABLE Delivery

(Part_ID INTEGER UNSIGNED,

Quantity INTEGER CHECK (Quantity >0),

FOREIGN KEY (Part_ID) REFERENCES Parts (Part_ID));

Определение внешнего ключа при модификации таблицы Delivery:

ALTER TABLE DELIVERY ADD FOREIGN KEY (Part_ID) REFERENCES Parts(Part_ID);

Описание созданной таблицы, полученное при помощи DESCRIBE:

1.9. Задание на лабораторную работу

1. Получить у преподавателя имя хоста, на котором работает сервер MySQL, имя вашей БД, а также логин и пароль для доступа к серверу MySQL.

2. Запустить командную строку cmd.exe и перейти в каталог, где находится файл mysql.exe.

3. Подключиться к серверу MySQL, запустив программу mysql.exe со следующими параметрами (host – имя хоста сервера, user – Ваш логин):

mysql -h host -u user –p

4. Установить пароль для доступа к серверу MySQL:

Set password for <имя_пользователя> = password('<пароль>');

5. Выполните запрос для получения версии MySQL-сервера, текущей даты и времени, имени текущего пользователя:

select version(), now(), current_date, current_time, user();

6. Создать базу данных с указанным преподавателем именем:

create database <имя_базы>;

7. Выполнить запрос для просмотра существующих БД:

show databases;

8. Выполнить команду для подключения к вашей базе данных:

USE <имя_базы>;

9. Закрыть соединение с сервером и выйти из программы mysql.exe

exit;

10. Написать командный файл (*.bat) для определения параметров соединения и запуска mysql.exe. Запустить mysql.exe при помощи созданного командного файла.

11. Написать сценарий (набор SQL-команд) для создания базы данных «Предприятие». Концептуальное описание БД «Предприятие»:

База данных «Предприятие» содержит информацию о работе некоторого промышленного предприятия, которое осуществляет сборку изделий (проектов) из деталей. В базе данных присутствуют следующие сущности: Детали, Проекты, Поставщики, Поставки, Города. Таблица «Детали» содержит описания деталей. Поля: Номер_детали [1] , Наименование, Материал, Вес. Таблица «Поставщики» содержит описания организаций, поставляющих детали. Поля: Номер_поставщика, Наименование, Номер_города. Таблица «Проекты» содержит описание проектов (сборочных изделий). Поля: Номер_проекта, Наименование, Номер_города. Таблица «Города» содержит описание городов, в которых могут находиться проекты и поставщики. Поля: Номер_города, Наименование. Каждый проект содержит некоторое количество различных деталей, поставляемых различными поставщиками в определенном количестве. Проект рассматривается как совокупность поставок. Таблица «Поставки» содержит записи о типе, количестве деталей и цене одной детали, поставляемых определенным поставщиком для определенного проекта. Поля: Номер_проекта, Номер_поставщика, Номер_детали, Количество, Цена, Дата_начала, Дата_конца.

В сценарии необходимо определить первичные и внешние ключи в таблицах. Для генерации значений первичных ключей использовать спецификатор AUTO_INCREMENT. Задать для первичных ключей требование обязательности данных NOT NULL. Задать для полей ВЕС, КОЛИЧЕСТВО и ЦЕНА беззнаковый целочисленный тип.

12. Добавить в начало сценария команды очистки базы данных. Внести в сценарий операторы заполнения таблиц тестовыми данными (не менее 5-и строк в каждой таблице). Запустить сценарий в пакетном режиме. При необходимости произвести отладку (устранение ошибок).

13. Проверить работу ограничений целостности CHECK и ограничений целостности внешних ключей путем добавления в таблицы некорректных данных.

1.10. Содержание отчета

Отчет должен содержать тексты и результаты выполнения всех команд (в виде скриншотов) с указанием пункта задания на лабораторную работу, а так же сценарий для создания БД «Предприятие» с комментариями.

Лабораторная работа 2. Работа с сервером MySQL при помощи клиента dbForge Studio for MySQL

2.1. Введение

dbForge Studio for MySQL является удобным профессиональным инструментом для разработки БД и выполнения широкого набора дополнительных функций на сервере MySQL.

dbForge Studio for MySQL является свободным программным продуктом. Его можно бесплатно получить через сайт разработчика https://www.devart.com/ru/dbforge.

Целью данной лабораторной работы является изучение основных приемов работы и выполнение действий по созданию и работе с БД на сервере MySQL при помощи клиента dbForge Studio for MySQL.

2.2. Создание соединения и подключение к серверу БД

После первого запуска dbForge Studio for MySQL предложит создать соединение с сервером. В диалоговом окне «Свойства соединения базы данных» необходимо ввести адрес (имя) компьютера, на котором работает сервер MySQL, логин и пароль для доступа к серверу MySQL. После этого можно открыть выпадающий список и выбрать базу данных (рис. 2.1). Далее желательно задать для соединения название, например, совпадающее с именем хоста сервера.

Рис. 2.1. Создание соединения с БД в dbForge Studio for MySQL

После подключения к серверу в окне «Проводник» будет доступен иерархический список доступных для пользователя БД и объектов, которые в них содержатся (рис. 2.2).

Рис. 2.2. Список БД в проводнике dbForge Studio for MySQL

2.3. Создание и редактирование таблиц

Для создания таблицы необходимо в проводнике БД в контекстном меню узла «Таблицы» выбрать пункт «Новая таблица». В появившемся окне можно задать имя таблицы и комментарий (рис. 2.3).

Рис.2.3. Создание таблицы

Для создания столбцов таблицы необходимо в окне редактирования таблицы в контекстном меню поля «Столбцы» выбрать пункт «Новый столбец» (рис. 2.4).

Рис.2.4. Создание столбца

Для создаваемого столбца таблицы можно задать имя, тип данных, длину, атрибуты допустимости пустых значений, атрибут уникальности, атрибут первичного ключа, значение по умолчанию и комментарий (рис. 2.5).

Рис. 2.5. Создание столбца

Редактирование таблицы и столбца производится аналогично (рис. 2.6).

Рис. 2.6 Редактирование таблицы

2.4. Определение внешних ключей

Для создания внешнего ключа в таблице необходимо в проводнике БД в контекстном меню узла «Таблицы» - «Ограничения» выбрать пункт «Новый внешний ключ» (рис. 2.7).

Рис. 2.7. Создание внешнего ключа

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

Рис. 2.8 Определение ограничения внешениго ключа

2.5. Просмотр и редактирование данных

Для просмотра и редактирования данных в таблице необходимо в окне редактирования таблицы перейти на вкладку «Данные» (рис. 2.9). Доступны операции редактирования значений столбцов, добавлении и удаления строк. Для фиксации изменений необходимо нажать кнопку «Запомнить». Изменения данных будут сохранены в БД.

Рис. 2.9. Просмотр и редактирование данных в таблице

2.6. Построение диаграммы БД

Для построения диаграммы БД необходимо выбрать пункт меню «Файл – Создать – Диаграмма БД» (Рис. 2.10). Появится пустое окно, куда можно перетащить необходимые таблицы из проводника БД.

Рис. 2.10. Создание Диаграммы БД

Связи между таблицами на диаграмме БД появятся автоматически. После добавления таблиц можно разместить таблицы на диаграмме с учетом удобства чтения диаграммы (минимального количества пересечений связей) (Рис. 2.11).

Рис. 2.11. Диаграмма БД

2.7. Экспорт схемы и данных

Экспорт схемы и данных позволяет автоматически сгенерировать сценарий на языке DDL, содержащий операторы создания объектов БД и наполнения ее данными. Такой сценарий удобно использовать для переноса БД на другой сервер или в качестве «твердой копии» БД. Для экспорта схемы и данных необходимо выбрать пункт «База данных – Экспорт схемы» в главном меню. Дальнейшие действия выполняются при помощи мастера экспорта схемы. Необходимо выбрать соединение, схему (БД), задать имя файла для сохранения сценария (рис. 2.12).

Рис.2.12. Мастер экспорта схемы

Далее необходимо выбрать вид информации для экспорта – структура, данные или и то и другое (рис. 2.13.)

Рис. 2.13. Выбор информации для экспорта

Следующим шагом предлагается выбрать объекты для экспорта (рис. 2.14). Это могут быть не только таблицы, но и представления, процедуры, функции, триггеры и др.

Рис. 2.14 Выбор объектов для экспорта

Если был выбран экспорт структуры и данных, то будет предложено выбрать таблицы из которых необходимо экспортировать данные (рис. 2.15).

Рис. 2.15 Выбор таблиц для экспорта данных

Заключительным шагом предлагается выбрать опции экспорта схемы (рис. 2.16). Среди перечисленных опций полезно выбрать опции «Включать выражения DROP» и «Включать IF EXIST в выражении DROP». Это позволит избежать ошибок связанных с запуском сценария в БД где уже были созданы объекты с идентичными именами.

Рис. 2.16 Выбор опций экспорта

После нажатия кнопки «Экспорт» появится сообщение с опцией открытия сценария в среде dbForge Studio for MySQL (рис. 2.17). Сценарий будет сохранен с указанным ранее именем.

Рис. 2.17 Завершение экспорта

Если была выбрана опция «Открыть скрипт» то после завершения экспорта откроется окно с полученным сценарием (рис. 2.18). Сценарий будет содержать операторы DDL и DML. Особенностью сценария является наличие в нем директивы отключения проверки ограничений целостности внешних ключей на время выполнения сценария:

/*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */;

Данная директива запоминает значение системной переменной FOREIGN_KEY_CHECKS и устанавливает ее значение равным 0. Это позволяет избежать ошибок, связанных с нарушением ограничений ссылочной целостности при удалении/создании объектов БД и добавления данных в таблицы в процессе выполнения сценария.

После выполнения сценария значение системной переменной восстанавливается:

/*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */;

Рис. 2.18.Сценарий, полученный в результате экспорта

2.8. Экспорт схемы и данных в DBForge Studio for MySQL версии 4.5

Для экспорта схемы и данных в версии 4.5 необходимо выбрать пункт «База данных – Создать резервную копию БД» в главном меню. Дальнейшие действия выполняются при помощи мастера резервирования БД. На вкладке «Общие» необходимо выбрать соединение, базу данных, задать путь и имя файла для сохранения сценария (рис. 2.19).

Рис. 2.19. Мастер резервирования БД

На вкладке «Содержимое» необходимо выбрать вид информации для экспорта – структура, данные или и то и другое, а также выбрать объекты для экспорта. Это могут быть не только таблицы, но и представления, процедуры, функции, триггеры и др. (рис. 2.20.)

Рис. 2.20. Выбор объектов для сохранения

На вкладке «Опции» предлагается выбрать опции экспорта схемы (рис. 2.21). Среди перечисленных опций полезно выбрать опции «Включать выражения DROP» и «Включать IF EXIST в выражении DROP». Это позволит избежать ошибок связанных с запуском сценария в БД где уже были созданы объекты с идентичными именами.

Рис. 2.21. Выбор опций для сохранения

После нажатия кнопки «Выполнить» появится сообщение о завершении создания резервной копии с опцией открытия сценария в среде dbForge Studio for MySQL и сохранения проекта (рис. 2.22). Сценарий будет сохранен с указанным ранее именем.

Рис. 2.22. Завершение создания резервной копии

2.9. Задание на лабораторную работу

1. Запустить dbForge Studio for MySQL, настроить подключение к серверу MySQL.

2. Просмотреть структуру и данные таблиц БД «Предприятие», созданной в лабораторной работе №1.

3. Построить диаграмму БД «Предприятие».

4. Произвести экспорт схемы (таблиц) и данных БД «Предприятие». Сравнить полученный сценарий со сценариями для создания объектов БД и наполнения БД данными, разработанными в лабораторной работе №1. Описать отличия и привести в отчете.

5. При помощи dbForge Studio for MySQL реализовать на сервере БД согласно теме курсового проекта по дисциплине.

6. Построить диаграмму БД согласно теме курсового проекта по дисциплине.

7. Произвести экспорт схемы (таблиц) и данных БД согласно теме курсового проекта по дисциплине.

2.10. Содержание отчета

Отчет должен содержать:

8. результаты выполнения всех пунктов работы (в виде скриншотов) с указанием пункта задания на лабораторную работу;

9. сценарий для создания БД «Предприятие» и наполнения ее данными, полученный при помощи экспорта схемы;

10. диаграмму БД «Предприятие»;

11. сценарий для создания БД согласно теме курсового проекта по дисциплине и наполнения ее данными;

12. диаграмму БД согласно теме курсового проекта по дисциплине.

Лабораторная работа 3. Разработка хранимых процедур и функций на сервере MySQL

3.1. Введение

Хранимые процедуры и функции являются важнейшим элементом современных промышленных СУБД. В MySQL хранимые процедуры поддерживаются начиная с версии 5. Хранимые процедуры и функции (а также триггеры как разновидность процедур) реализуются в виде подпрограмм. При этом процедуры и функции называются хранимыми т.к. они хранятся в базе данных наряду с другими объектами (таблицами, ограничениями и т.д.). Хранимые подпрограммы представляют собой набор команд SQL. Кроме операторов SQL в хранимой подпрограмме могут быть использованы основные элементы свойственные любому языку программирования – переменные, операторы ветвления, циклы и др.

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

3.2. Создание, запуск и удаление простой процедуры

Для создания простой процедуры в клиенте mysql.exe можно выполнить следующие операторы:

DELIMITER //

CREATE PROCEDURE Hello_World()

BEGIN

SELECT(‘Hello, world!’);

END

//

В данном примере первая строка задает последовательность символов, которая будет использоваться в качестве признака завершения ввода. Обычно для этого используется точка с запятой, однако, в приведенном выше примере точка с запятой уже используется в качестве разделителя операторов в теле функции.

Во второй строке следует оператор создания процедуры CREATE PROCEDURE, после которого указывается имя процедуры. Создаваемая процедура не имеет параметров, поэтому скобки после имени процедуры – пустые.

Далее следует конструкция BEGIN.. END, в которую должны быть заключены все операторы процедуры. Операторы разделяются символом точка с запятой.

Для запуска процедуры на выполнение можно использовать команду CALL:

CALL Hello_World;

Для удаления процедуры можно использовать команду DROP PROCEDURE:

DROP PROCEDURE Hello_World;

3.3. Создание, запуск и удаление простой функции

Для создания простой процедуры в клиенте mysql.exe можно выполнить следующие операторы:

DELIMITER //

CREATE FUNCTION Hello_World()

RETURNS VARCHAR(20)

BEGIN

RETURN ‘Hello, world!’;

END

//

Для запуска функции необходимо ввести

SELECT Hello_World()//

3.4. Простые и системные переменные

Объявить переменную в хранимой подпрограмме можно в любом месте тела подпрограммы (внутри блока BEGIN.. END). Синтаксис оператора объявления переменной:

DECLARE имя [, имя]... тип_данных [DEFAULT значение]

Объявить переменную в хранимой подпрограмме можно в любом месте тела подпрограммы. Если параметр DEFAULT отсутствует, то переменная инициализируется со значением NULL.

Для присвоения значения переменной может быть использован оператор SET. В следующем примере переменной S присваивается текстовое значение, которое затем выводится на экран:

CREATE PROCEDURE Hello_World()

BEGIN

DECLARE S VARCHAR(20);

SET S=‘Hello, world!’;

SELECT(S);

END

//

Результат вызова процедуры на выполнение:

Иногда бывает необходимо присвоить переменной значение, возвращаемое в результате запроса. Это можно сделать при помощи оператора SELECT..INTO. При этом запрос должен возвращать только одну строку. Если запрос возвращает пустой результат, это приведет к ошибке 1329 (No data). Если запрос содержит более одной строки, это приведет к ошибке 1172 (Result consisted of more than one row). Количество строк, возвращаемых запросом, можно ограничить опцией LIMIT оператора SELECT. Данная опция имеет два параметра. Первый параметр указывает смещение возвращаемого набора строк относительно начала, второй – количество возвращаемых строк. При использовании опции только с одним параметром он интерпретируется как количество возвращаемых строк от начала результата. Таким образом, совместно с оператором SELECT..INTO можно использовать опцию LIMIT 1. Следующая процедура выводит наименование самой тяжелой детали:

CREATE PROCEDURE Heavy()

BEGIN

DECLARE S VARCHAR(20);

SELECT weight INTO S FROM Parts ORDER BY Weight DESC LIMIT 1;

SELECT(S);

END

//

В процессе выполнения оператора SELECT..INTO выполняется неявное приведение типа возвращаемого запросом значения типу переменной:

CREATE PROCEDURE Parts_count()

BEGIN

DECLARE S VARCHAR(20);

SELECT count(*) INTO S FROM Parts;

SELECT(S);

END

//

Разница между простыми и системными переменными в том, что системные переменные доступны извне хранимой процедуры. Системную переменную не нужно инициализировать. Разница в простой и системной переменной пользовании префикса @ в имени системной переменной.

SET @S=‘Hello, world!’;

Значение системной переменной можно узнать после выполнения хранимой процедуры:

CREATE PROCEDURE Parts_count()

BEGIN

SELECT count(*) INTO @S FROM Parts;

END

//

3.5. Параметры процедур и функций

Параметры процедуры или функции указываются в операторе CREATE после имени в скобках. Если параметров нет, то необходимо указывать пустые скобки. Параметры бывают типов IN, OUT INOUT (см. таблицу).

Режим Предназначение Использование параметра
IN Только для чтения Значение параметра может применяться, но не может быть изменено в модуле
OUT Только для записи В модуле можно присваивать значение параметру, но нельзя использовать его.
IN OUT Для чтения и записи В модуле можно использовать и изменять значение параметра

Пример процедуры, в которой используется один параметр IN и один параметр OUT:

CREATE PROCEDURE Parts_count(IN Mat VARCHAR(20),OUT PNum INT)

BEGIN

SELECT count(*) INTO PNum FROM Parts WHERE Material=Mat;

END

//

CALL Parts_count(‘Rubber’,@S)//

Пример функции с параметром:

CREATE FUNCTION Hello_World(S VARCHAR(20))

RETURNS VARCHAR(20)

BEGIN

RETURN CONCAT(‘Hello ’,S,’!!!’);

END

//

Для запуска функции необходимо ввести

SELECT Hello_World(‘Root’)//

3.6. Операторы управления ходом выполнения программы

Условный оператор IF..THEN имеет следующий синтаксис:

IF <условие> THEN <оператор 1>

[ELSEIF <условие> THEN <оператор 2>]...

[ELSE <оператор 3>]

END IF

Оператор CASE имеет следующий синтаксис:

CASE case_value

WHEN when_value THEN statement_list

[WHEN when_value THEN statement_list]...

[ELSE statement_list]

END CASE

Самым простым оператором цикла является оператор LOOP, имеющий следующий синтаксис:

[Метка_начала:] LOOP

<оператор>

END LOOP [Метка_конца]

Операторы цикла выполняются до тех пор, пока внутри цикла не будет выполнен оператор LEAVE < Метка_начала >, который прерывает цикл с указанной меткой.

Оператор цикла с постусловием имеет следующий синтаксис:

REPEAT

<оператор>

UNTIL <условие_выхода>

END REPEAT

Цикл будет выполняться до тех пор, пока условие выхода не станет истинным.

Оператор цикла с предусловием имеет следующий синтаксис:

WHILE <условие_выполнения> DO

<оператор>

END WHILE

Цикл будет выполняться до тех пор, пока условие выполнения будет истинным.

3.7. Курсоры

Курсор представляет собой временную таблицу, получаемую в результате запроса, которая служит для построчной обработки данных. Курсор позволяет в цикле «перебирать» строки, выполняя над ними необходимые действия. Таки образом, курсор является таблицей, возможности которой выходят за рамки классической реляционной модели (в классическом отношении отсутствует понятие порядка следования строк – строки представляют собой множество).

Для объявления курсора используется следующий оператор:

DECLARE <имя_курсора> CURSOR FOR <SQL-выражение>;

Прежде чем курсор может быть использован, его необходимо открыть:

OPEN <имя_курсора>;

После открытия указатель курсора устанавливается на первую строку. Для доступа к текущей строке открытого курсора используется оператор:

FETCH <имя_курсора> INTO <имя_переменной> [,<имя_переменной>]...

Этот оператор помещает значения строки курсора в переменные, количество и типы данных которых соответствуют схеме (столбцам) курсора. После выполнения оператора FETCH происходит автоматическое продвижение на следующую строку курсора. Если более нет доступных строк (достигнута последняя строка) происходит изменение значения переменной SQLSTATE в 02000. Для обработки этого события необходимо установить обработчик: HANDLER FOR SQLSTATE '02000'.

Следующий оператор закрывает курсор:

CLOSE <имя_курсора>;

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

Для примера создадим процедуру, которая изменяет имя каждой детали с определенным именем на имя, формируемое как «Имя-N», где N – порядковый номер в списке всех деталей «Gasket» в порядке возрастания веса детали. Имя детали передается в качестве параметра.

CREATE PROCEDURE Parts_rename(PName VARCHAR(20))

BEGIN

DECLARE Done INT DEFAULT 0;

DECLARE S VARCHAR(20);

DECLARE N,I INTEGER;

DECLARE Cur1 CURSOR FOR SELECT Part_ID, Part_name FROM Parts WHERE Part_name=PName ORDER BY WEIGHT;

DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;

OPEN Cur1;

SET I=1;

REPEAT

FETCH Cur1 INTO N,S;

IF Done=0 THEN UPDATE Parts SET Part_name=CONCAT(S,’-’,I) WHERE Part_ID=N;

END IF;

SET I=I+1;

UNTIL Done END REPEAT;

CLOSE Cur1;

END

Содержимое таблицы до выполнения процедуры:

Содержимое таблицы после выполнения процедуры:

3.8. Задание на лабораторную работу

1. Для БД «Предприятие» реализовать и проверить работу следующих процедур и функций:

a. Реализовать функцию, которая возвращает количество различных городов, в которых находятся поставщики определенного проекта (входной параметр);

b. Реализовать функцию, которая возвращает количество различных городов, в которых находятся проекты, обеспечиваемые определенным поставщиком (входной параметр);

c. Реализовать функцию, которая возвращает количество различных городов, в которых находятся поставщики определенной детали (входной параметр);

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

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

2. Написать процедуру, которая изменяет имя каждой детали на имя, формируемое как «Имя_детали-N», где N – порядковый номер в списке всех одноименных деталей в порядке возрастания веса детали.

3.9. Содержание отчета

В отчете привести код процедур и функций, результаты выполнения, содержимое таблиц до и после (при необходимости) выполнения процедур и функций.

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

CREATE FUNCTION Qty_of_Prj_Towns(S VARCHAR(20))

RETURNS INTEGER

BEGIN

DECLARE N INTEGER;

SELECT COUNT(*) INTO N FROM Towns WHERE Town_ID IN (SELECT Projects.Town_ID FROM Parts, Projects, Supply WHERE Parts.Part_ID= Supply.Part_ID AND Projects.Project_ID=Supply.Project_ID AND Parts.Part_name=S);

RETURN N;

END

Лабораторная работа 4. Исключения и триггеры

4.1. Исключения

Исключения, или исключительные ситуации – это ошибки, которые возникают в процессе выполнения инструкций SQL. MySQL перехватывает ошибки и реагирует на них при помощи т.н. обработчиков (handlers). Механизм функционирования обработчиков ис­ключений позволяет четко отделить код обработки ошибок от исполняемых опера­торов.

Когда происходит ошибка, инициируется исключение. В результате выполнение исполняе­мого блока прерывается и управление передается отдельному разделу исключений в текущем блоке, если таковой имеется. После обработки исключения возврат в тот блок, из которого оно было инициировано, невозможен, поэтому управле­ние переходит во внешний блок.

Существует два вида исключений:

Системное исключение. Определяется в MySQL и обычно инициируется ядром СУБД, обнаружившим ошибку.

Пользовательское исключение. Определяется программистом, следовательно оно специфично для данного приложения.

Системные исключения возникают при попытке выполнения недопустимых SQL-инструкций. О возникновении исключения можно узнать из сообщения клиента mysql.exe. Например, при попытке выбрать данные из несуществующей таблицы произойдет следующее:

Информация о произошедшем исключении стоит из трех элементов:

1. Число – код ошибки (1146). Этот код определен в MySQL и несовместим с другими СУБД.

2. Пятисимвольный код системной переменной SQLSTATE (42S02). Эти значения определяются в стандартах ANSI SQL и ODBC и стандартизированы в большей степени.

3. Текстовое описание ошибки (Table ‘database1.no_such_table’ doesn’t exist).

При создании обработчика исключения можно использовать различные способы идентификации исключения. Синтаксис оператора создания исключения:

DECLARE <тип_обработчика> HANDLER

FOR <идентификатор_исключения> [,<идентификатор_исключения>]...

<операторы>;

Здесь параметр <тип_обработчика> может принимать значения CONTINUE, EXIT или UNDO. Для обработчиков типа CONTINUE выполнение программы, вызвавшей исключение, продолжается после выполнения кода обработчика. Для обработчиков типа EXIT выполнение текущего блока BEGIN…END программы, вызвавшей исключение, прерывается. Обработчики типа UNDO не поддерживаются в версии MySQL 5.5.

Параметр <идентификатор_исключения> может представлять собой значение SQLSTATE, имя исключения, которое было задано при его создании оператором DECLARE... CONDITION, а также одно из ключевых слов:

SQLWARNING – синоним для значений SQLSTATE, начинающихся с 01

NOT FOUND - синоним для значений SQLSTATE, начинающихся с 02

SQLEXCEPTION - синоним для значений SQLSTATE, НЕ начинающихся с 00, 01 или 02

Например, создадим процедуру, в которой определим обработчик для исключения с кодом 42S02:

CREATE PROCEDURE h()

BEGIN

DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SELECT (‘Таблица не существует!’);

SELECT * FROM No_such_table;

END;

Вариант процедуры с предварительно заданным именем исключения при помощи DECLARE... CONDITION:

CREATE PROCEDURE h()

BEGIN

DECLARE No_my_table CONDITION FOR SQLSTATE '42S02';

DECLARE CONTINUE HANDLER FOR No_my_table SELECT (‘Таблица не существует!’);

SELECT * FROM No_such_table;

END;

Вариант процедуры с использованием синонима SQLEXCEPTION для идентификации исключения:

CREATE PROCEDURE h()

BEGIN

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SELECT (‘Таблица не существует!’);

SELECT * FROM No_such_table;

END;

Следующий пример демонстрирует продолжение выполнения программы после обработчика типа CONTINUE:

CREATE PROCEDURE h()

BEGIN

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SELECT (‘Таблица не существует!’);

SELECT * FROM No_such_table;

SET @A=1;

END;

Если использовать обработчик типа EXIT, то выполнение программы прерывается:

CREATE PROCEDURE h()

BEGIN

DECLARE EXIT HANDLER FOR SQLEXCEPTION SELECT (‘Таблица не существует!’);

SELECT * FROM No_such_table;

SET @A=1;

END;

Следующая процедура содержит обработчик исключения 23000 (ER_DUP_KEY)

CREATE PROCEDURE h()

BEGIN

DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SELECT ‘Первичный ключ уже существует’;

INSERT INTO Parts VALUES (9,’Spoon’,’Wood’,30);

INSERT INTO Parts VALUES (9,’Spoon’,’Wood’,30);

END;

Оператор SIGNAL

Оператор SIGNAL позволяет вызвать пользовательское исключение и сообщить обработчику или внешней программе информацию о произошедшей ошибке. Синтаксист оператора SIGNAL:

SIGNAL <идентификатор_исключения>

[SET <параметр> = <значение>, [<параметр> = <значение>]...]

Здесь параметр <идентификатор_исключения> может представлять собой значение SQLSTATE или имя исключения, которое было задано при его создании оператором DECLARE... CONDITION. Значение SQLSTATE не должно начинаться с цифр ‘00’. Для пользовательского исключения следует использовать значение SQLSTATE 45000.

Оператор SIGNAL может включать опцию SET, после которой следуют пары <параметр>=<значение>, разделенные запятыми.

Пример:

CREATE PROCEDURE p (val INT)

BEGIN

DECLARE my_error CONDITION FOR SQLSTATE ‘45000’;

DECLARE EXIT HANDLER FOR my_error SELECT(‘Произошла ошибка!’);

IF val = 0 THEN

SIGNAL my_error;

END IF;

END;

Пример с использованием параметра MESSAGE_TEXT:

CREATE PROCEDURE p (val INT)

BEGIN

DECLARE my_error CONDITION FOR SQLSTATE ‘45000’;

IF val = 0 THEN

SIGNAL my_error SET MESSAGE_TEXT = ‘Произошла ошибка!’;

END IF;

END;

4.2. Триггеры

Триггеры - это особые хранимые процедуры, выполняемые в ответ на происхо­дящие в базе данных события. Они относятся к числу наиболее важных элемен­тов промышленных приложений базы данных. Основным назначением триггеров является поддержка ограничений целостности, которые не реализуются при помощи внешних ключей и ограничений, накладываемых на значение столбца (NOT NULL, CHEK и т.д.).

Триггеры уровня инструкций языка манипулирования данными (триггеры DML) запускаются после вставки, обновления или удаления строки конкретной табли­цы. Это наиболее распространенный тип триггеров, особенно часто применяемый разработчиками.

Триггер BEFORE. Вызывается до внесения каких-либо изменений, в том чис­ле до вставки записи (BEFORE INSERT).

Триггер AFTER. Выполняется после того, как производятся все изменения, в частности после операции вставки записи (AFTER INSERT).

Существуют, также, следующие виды триггеров:

Триггер уровня инструкции. Выполняется для отдельной SQL-инструкции, которая может обрабатывать одну или более записей базы данных.

Триггер уровня записи. Вызывается для отдельной записи, обрабатываемой SQL-инструкцией. Если, предположим, таблица books содержит 1000 строк, то следующая инструкция UPDATE модифицирует все эти строки: UPDATE books SET title = UPPER (title); И если для данной таблицы определен триггер уровня записи, он будет запу­щен 1000 раз.

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

Псевдозапись OLD. Структура данных с именем OLD, которая так же выгля­дит и обладает такими же свойствами, как и запись таблицы. Эта псевдозапись Доступна только внутри триггеров обновления и удаления; она содержит зна­чения модифицируемой записи до внесения изменений.

Синтаксис оператора создания триггера:

CREATE TRIGGER <имя_триггера> {BEFORE | AFTER} <событие_БД>

ON <имя_таблицы> FOR EACH ROW

BEGIN

<операторы>

END

<событие_БД> - определение типа DML-инструкции, с которой связывается триггер: INSERT, UPDATE или DELETE. У каждой таблицы для каждого события может существовать только один триггер.

Триггер, осуществляющий проверку веса детали при добавлении ее в таблицу Parts (вес не должен превышать заданного значения)

CREATE TRIGGER Check_Weight BEFORE INSERT ON Parts FOR EACH ROW

BEGIN

DECLARE Wrong_weight CONDITION FOR SQLSTATE ‘45000’;

IF NEW.Weight > 1000 THEN

SIGNAL Wrong_weight SET MESSAGE_TEXT = ‘Вес детали превышает 1000!’;

END IF;

END

Триггер, осуществляющий проверку на совпадение наименований деталей:

CREATE TRIGGER Check_Part_Name BEFORE INSERT ON Parts FOR EACH ROW

BEGIN

DECLARE Duplicate_part_name CONDITION FOR SQLSTATE ‘45000’;

DECLARE N INTEGER;

SELECT COUNT(*) INTO N FROM Parts WHERE Part_name=NEW. Part_name;

IF N > 0 THEN

SIGNAL Duplicate_part_name SET MESSAGE_TEXT = ‘Такая деталь уже есть в базе!’;

END IF;

END

Триггер, который удаляет все детали, наименование которых совпадает с удаляемой деталью (не будет работать):

CREATE TRIGGER Delete_the_same_parts AFTER DELETE ON Parts FOR EACH ROW

BEGIN

DELETE FROM Parts WHERE Part_name=OLD.Part_name;

END

https://doc.prototypes.ru/database/mysql/triggers/info/

4.3. Задание на лабораторную работу

1. Реализовать триггер, автоматически добавляющий к имени детали символ ‘-‘ и порядковый номер детали (с учетом уже имеющихся одноименных деталей в базе) при добавлении каждой новой детали в таблицу. Т.е. детали должны получать уникальные имена «Деталь-1», «Деталь-2», «Деталь-3» и т.д.

2. Реализовать триггер, ограничивающий максимальную суммарную стоимость всех деталей, поставляемых для определенного проекта.

3. Реализовать триггер, ограничивающий максимальный суммарный вес всех деталей, поставляемых для определенного проекта.

4. Реализовать триггер, который разрешает только поставки, в которых поставщик и проект находятся в одном городе.

5. Реализовать триггер, который удаляет проект при удалении последней поставки для данного проекта.

4.4. Содержание отчета

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

Лабораторная работа 5. Работа с сервером МуSQL в Borland C++ Builder

5.1. Технологии программного доступа к данным

https://mf.grsu.by/other/lib/db/part4.html

https://www.comprice.ru/articles/detail.php?ID=40308

https://delphi.support.uz/index.php?type=glava&id=22

5.2. Компоненты ActiveX Data Objects

Компоненты ActiveX Data Objects находятся на вкладке ADO:

Существует возможность установить соединение с БД для следующих компонентов ADO:

· ADOConnection

· ADOCommand

· ADODataSet

· ADOTable

· ADOQuery

· ADOStoredProc

Компонент ADOConnection может использоваться как базовый для подключения к БД остальных компонентов, или же остальные компоненты могут использоваться отдельно от ADOConnection.

Самым коротким путем для получения данных является использование компонента ADODataSet, который представляет собой таблицу (отношение), получаемую из БД с использованием SQL запроса.

Большинство визуальных компонентов получают данные от компонента DataSource, который находится на вкладке DataAccess:

Для непосредственного отображения на экране и редактирования данных используются компоненты, расположенные на вкладке Data Controls:

Компоненты вкладки Data Controls представляют собой аналоги для визуальных компонентов вкладки Standard, однако для них определены свойства, связывающие их с объектом DataSource.

Компоненты DBLookupListBox и DBLookupComboBox отличаются тем, что могут быть связаны одновременно с двумя источниками данных DataSource – один из них используется для получения значения, а второй – для формирования элементов списка выбора.

5.3. Создание главной формы приложения для работы с БД. Соединение с базой данных

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

Создайте новый проект в C++Builder и поместите на главную форму приложения компонент MainMenu (вкладка Standard). При помощи Menu Designer создайте пункты меню «Детали», «Поставщики», «Проекты», «Поставки».

Поскольку в приложении будет использоваться несколько компонентов ADODataSet, то целесообразно создать один объект ADOConnection, который будет задавать параметры строки соединения с сервером БД для всех компонентов ADODataSet.

Поместите на главную форму компонент ADOConnection (вкладка ADO).

Теперь необходимо определить все параметры соединения с БД. Для этого необходимо в инспекторе объектов найти компонент ADOConnection1 и нажать на кнопку для формирования строки соединения ConnectionString:

В появившемся окне выбрать пункт Use Connection String. Нажать кнопку Build:

Выбрать поставщик данных Microsoft OLE DB Provider for ODBC Drivers:

Нажать кнопку «Далее». На вкладке «Подключения» выбрать пункт «Использовать строку подключения», нажать кнопку «Сборка»:

В окне «Выбор источника данных» выбрать вкладку «Источник данных компьютера». Нажать кнопку «Создать»:

Выбрать тип источника данных «Пользовательский», нажать кнопку «Далее»:

Выбрать драйвер MySQL ODBC 3.51 Driver. Нажать кнопку «Далее»:

Нажать кнопку «Готово»:

В окне «Connector/ODBC» ввести имя источника данных (произвольное), имя сервера, имя пользователя, пароль. Выбрать базу данных:

Для корректной работы с символами кириллицы в данных текстового типа на вкладке Connect Option задать для параметра Initial Statement значение set names ‘cp1251’.

Нажать кнопку «Test» и проверить подключение. Если все правильно, должно появиться следующее окно:

Нажать «Ок». В окне «Выбор источника данных» убедиться в создании и выборе нового источника данных и нажать «ОК»:

Подтвердить параметры источника данных кнопкой «Ок»:

В окне «Свойства связи с данными» ввести имя пользователя и пароль, отметить пункт «Разрешить сохранение пароля», ввести начальный каталог (базу данных):

Нажать кнопку «Проверить подключение»:

Появится окно со строкой соединения. Нажать «Ок»:

5.4. Создание простой формы с использованием компонента DataGrid (форма «Детали»)

Для создания простой формы доступа к данным одной таблицы необходимо создать новую форму (Form2), определить для свойства Caption значение «Детали» и поместить на форму компоненты ADODataSet, DataSource, DBGrid, DBNavigator:

Для связи компонента ADODataSet1 с компонентом ADOConnection1 необходимо в модуль формы Unit2.cpp добавить директиву для включения заголовочного файла главной формы:

В инспекторе объектов для ADODataSet1 в поле свойства Connection указать Form1->ADOConnection1, в поле свойства CommandText ввести текст SQL-команды для выбора всех записей из таблицы «Детали» select * from parts, присвоить свойству Active значение True:

В инспекторе объектов для DataSource1 в поле свойства DataSet выбрать ADODataSet1:

В инспекторе объектов для DBNavigator1 и DataGrid1 в поле свойства DataSource выбрать DataSource1:

После этого в компоненте DBGrid1 в режиме разработки должны появиться данные из таблицы Parts:

Для того чтобы какая-либо форма «Детали» отображалась при выборе пункта меню «Детали» необходимо в модуль главной формы добавить директиву для включения заголовочного файла формы #include "Unit2.h", в главной форме в режиме разработки выбрать соответствующий пункт меню и создать обработчик следующего вида:

void __fastcall TMain::N1Click(TObject *Sender)

{

Form2->Show();

}

После запуска приложения после выбора пункта меню «Детали» будет открыта форма Form2. В ней при помощи компонента DBNavigator можно перемещать курсор по записям набора данных вперед и назад, вставлять и удалять строки, отменять и фиксировать изменения:

5.5. Использование компонентов DBEdit, и DBLookupComboBox. Программный доступ к свойствам и методам компонента ADODataSet (форма «Поставщики»)

Для отображения и редактирования данных можно использовать компоненты DBEdit, DBLookupListBox и DBLookupComboBox, которые находятся на вкладке Data Controls. Так можно разработать форму, в которой элементы данных будут отображаться не в виде таблицы (DataGrid), а будут размещены произвольным удобным пользователю образом. Кроме того, стандартный элемент управления DBNavigator так же не всегда удобен для пользователя, при этом нет возможности изменения внешнего вида кнопок.

Создайте новую форму (Form3), задайте значение свойства Caption как «Поставщики» и поместите на нее компоненты ADODataSet, DataSource, задайте свойства компонентов аналогично предыдущей форме. При этом необходимо в модуль формы Unit3.cpp добавить директиву для включения заголовочного файла главной формы.

Значение свойства CommandText компонента ADODataSet1 необходимо определить как select * from providers.

Поместите на форму компоненты Label (вкладка Standard) и DBEdit (вкладка DataControls) и кнопки Button как показано на рисунке:

Свяжите компоненты DBEdit с полями набора данных ADODataSet1, задав для них свойства DataSource и DataField:

Для реализации механизма навигации по базе создайте обработчики для кнопок:

void __fastcall TForm3::Button1Click(TObject *Sender)

{

ADODataSet1->Prior(); // Переход к следующей записи

}

//--------------------------------------------------------------

void __fastcall TForm3::Button2Click(TObject *Sender)

{

ADODataSet1->Next(); // Переход к предыдущей записи

}

//--------------------------------------------------------------

void __fastcall TForm3::Button3Click(TObject *Sender)

{

ADODataSet1->Post(); // Сохранение изменений

}

//--------------------------------------------------------------

void __fastcall TForm3::Button4Click(TObject *Sender)

{

ADODataSet1->Insert(); // Вставка новой записи

}

//--------------------------------------------------------------

Свяжите форму со вторым пунктом системного меню главной формы. Проверьте работу формы в режимах просмотра, редактирования и добавления данных:

Очевидным недостатком данной формы является недостаточная «дружелюбность» интерфейса. Так, например, в поле «Город поставщика» было бы актуально отображать не номер города, а его имя (из таблицы «Города»), а при редактировании поля предлагать пользователю выбор из имеющихся в таблице «Города» названий городов.

Для решения данной задачи можно воспользоваться компонентом DBLookupComboBox. Данный компонент может быть связан одновременно с двумя источниками данных, один из которых является «основным», а второй содержит значения элемента выпадающего списка.

Поместите на форму еще один компонент ADODataSet и DataSource. Для компонента ADODataSet2 задайте свойство Connection, свойство CommandText (select * from towns), Active (True). Для компонента DataSource2 задайте свойство DataSet (ADODataSet2).

Поместите на форму компонент DBLookupComboBox вместо DBEdit3. Задайте для него следующие свойства:

Свойство Значение
DataSource DataSource1
DataField Town_ID
ListSource DataSource2
ListField Town
KeyField Town_ID

Проверьте работу формы. Теперь вместо номеров городов пользователь оперирует именами городов, что повышает удобство работы и не требует от пользователя знания номеров городов.

5.6. Реализация поиска и фильтрации в базе. Программный доступ к элементам набора данных (форма «Проекты)

Отбор данных (поиск, фильтрация) в БД является наиболее распространенной операцией. Такая операция стандартно реализуется посредством SQL-запроса к базе. Однако, реализация такой операции в пользовательском интерфейсе возможна большим количеством разнообразных способов. В данном разделе приведен пример реализации поиска-фильтрации поставщика по наименованию с помощью компонента ComboBox. При этом преимуществом такого подхода является то, что одно и то же поле используется как для ввода подстроки поиска, так и для отображения и редактирования значения.

Создайте новую форму (Form 4). Поместите на форму компоненты ADODataSet, DataSource, DBGrid. Последний понадобится для отображения множества записей, найденных в результате операции фильтрации. Задайте значение свойства Caption как «Проекты».

Значение свойства CommandText компонента ADODataSet1 необходимо определить как select * from projects. Свойство Connection компонента ADODataSet1 определить как Form1->ADOConnection1. При этом в модуль формы Unit4.cpp добавить директиву для включения заголовочного файла главной формы.

Поместите на форму компонент ComboBox, задав пустое значение для свойства Text и значение False для свойства AutoComplete:

Механизм поиска будет заключаться в динамическом формировании набора данных ADODataSet1 и элементов выпадающего списка ComboBox посредством формирования SQL-запроса с предикатом LIKE, где в качестве шаблона сравнения используется подстрока, введенная пользователем в поле ComboBox1.

В инспекторе объектов найдите событие OnChange компонента ComboBox1. Создайте обработчик события со следующим кодом:

//--------------------------------------------------------------void __fastcall TForm4::ComboBox1Change(TObject *Sender)

{

ADODataSet1->Close();

ADODataSet1->CommandText="select * from Projects where Project like '"+ComboBox1->Text+"%'";

ADODataSet1->Open();

AnsiString S="";

// Сохранение подстроки поиска в S

S=ComboBox1->Text;

ComboBox1->Clear();

// Заполнение выпадающего списка ComboBox1

for(int i = 0; i < ADODataSet1->RecordCount; i++)

{

ComboBox1->Items->Add(ADODataSet1->Fields->Fields[1]->AsString);

ADODataSet1->RecNo++;

}

// Очистка и перезапись подстроки поиска в поле ComboBox1

ComboBox1->Text="";

ComboBox1->Text=S;

// Установка курсора в конец подстроки в поле ComboBox1

ComboBox1->SelStart=S.Length();

ComboBox1->SelLength=0;

}

//--------------------------------------------------------------

В инспекторе объектов найдите событие OnSelect компонента ComboBox1 и пустой обработчик события (это необходимо для того, чтобы выбранное из списка значение сохранялось в поле редактирования):

//--------------------------------------------------------------

void __fastcall TForm4::ComboBox1Select(TObject *Sender)

{

//

}

//--------------------------------------------------------------

Свяжите форму с пунктом «Проекты» меню главной формы и проверьте работу механизма поиска и фильтрации:

5.7. Сортировка строк в компоненте DBGrid

Для того, чтобы реализовать сортировку строк в компоненте DBGrid (например, в форме «Проекты») по клику на заголовке соответствующего столбца необходимо добавить для события OnTitleClick обработчик следующего вида:

void __fastcall TForm4::DBGrid1TitleClick(TColumn *Column)

{

if (ADODataSet1->Active)

if ((ADODataSet1->Sort.Pos(Column->FieldName)>0) &&

(ADODataSet1->Sort.Pos("ASC")>0))

ADODataSet1->Sort=Column->FieldName+" DESC";

else ADODataSet1->Sort=Column->FieldName+" ASC";

}

5.8. Создание форм с выбором режима просмотра или редактирования. Работа с датой и временем (форма «Поставки»)

Обычно к пользовательскому интерфейсу предъявляется требование дружественности, которое заключается в следующем:

- предоставление ясной и полной информации;

- интуитивно понятная организация;

- контроль вводимых пользователем данных;

- защита от неправильных действий;

- подсказки и предупреждения.

Для реализации примера дружественной формы создайте форму «Поставки» (Form5). Свяжите ее с соответствующим пунктом системного меню. Поместите на форму компоненты Label, DBLookupComboBox, Button, ADODataSet, DataSource как показано на рисунке:

Установите для свойства Enabled кнопок Выбрать и Сохранить значение False.

Добавьте в модуль формы Unit5.cpp директиву для включения заголовочного файла главной формы.

Для всех компонентов ADODataSet свойство Connection определите как Form1->ADOConnection1.

Задайте свойства CommandText для компонентов ADODataSet в соответствии с таблицей:

Компонент Значение CommandText
ADODataSet1 select * from supply
ADODataSet2 select * from parts
ADODataSet3 select * from projects
ADODataSet4 select * from providers

Для всех компонентов DataSource задайте свойство DataSet, связав каждый DataSource со «своим» DataSet.

Задайте свойства для компонентов DBLookupComboBox и DBEdit в соответствии с таблицей:

Компонент DataSource DataField ListSource ListField KeyField ReadOnly
DBLookupComboBox1 DataSource1 Part_ID DataSource2 Part_name Part_ID True
DBLookupComboBox2 DataSource1 Project_ID DataSource3 Project Project_ID True
DBLookupComboBox3 DataSource1 Provider_ID DataSource4 Provider Provider_ID True
DBEdit1 DataSource1 Begin_Date - - - True
DBEdit2 DataSource1 End_Date - - - True
DBEdit3 DataSource1 Quantity - - - True
DBEdit4 DataSource1 Price - - - True

Для реализации ввода даты и времени создайте форму «Ввод даты и времени» (Form6). Поместите на форму компоненты DateTimePicker, Label, Button, как показано на рисунке.

Установите для первого компонента DateTimePicker значение свойства Kind равное dtkDate, а для второго dtkTime.

Для передачи в форму «Ввод даты и времени» параметра выбора даты начала или конца поставки необходимо в заголовочном файле главной формы Unit1.h в разделе public описать переменную int n.

Для управления компонентами формы «Поставки» создайте в модуле формы следующие обработчики:

void __fastcall TForm5::Button3Click(TObject *Sender)

{

ADODataSet1->Next(); // Переход к следующей записи

}

//-----


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: