[БЕЗ_ЗВУКА] В этом видео мы будем рассматривать, каким образом можно работать с SQL базами данных и с Go. Для начала посмотрим на эту страницу. Тут мы видим простой список каких-то элементов с возможностью создания новых, редактирования и удаления. Все это в данном уроке реализовано через MySQL, для Postgres отличия будут очень незначительные, и файлы для него вы можете найти в файлах к уроку. Итак, сначала посмотрим на схему данных. Вот у нас есть таблица, items: есть id, title, desctiption и поле updated. Обратите внимание, что поле updated у нас может быть NULL. Это важный момент, про это я буду рассказывать. И ставим несколько элементов. Хорошо, схема базы простая, ничего сложного в ней нету. Теперь просмотрим, каким образом начать работать с этим с кода. В стандартной библиотеке Go нету встроенного драйвера никакого, однако авторы стандартной библиотеки поступили очень мудро и сделали интерфейс. Называется он database/sql. И этот интерфейс уже могут реализовывать разные базы данных, за счет чего вы сможете более или менее одинаково работать с MySQL, Postgres, Oracle, SQL и какими-нибудь другими базами данных. То есть для того чтобы использовать SQL, вам нужно подрубить его для начала. Теперь я подключаю драйвер, в данном случае это драйвер MySQL. Обратите внимание: я подключаю его с использованием пустой переменной. Для чего это надо? Дело в том, что сам драйвер он не экспортирует, по сути, никакие функции, и я не буду им пользоваться в коде. Я буду пользоваться везде через database/sql. Однако драйвер внутри себя через функцию init регистрирует обработчик MySQL, который будет вызван далее. Но подчеркивание, чтобы компилятор Go не ругался на неиспользуемый пакет. Вот. И рассматривать мы будем наш item, который мапится из строчки MySQL вот в такую структуру: есть id, title, description и поле updated, которое представлено каким-то полем NullString, об этом я буду рассказывать. Итак, теперь начнем с подключения к базе. Подключение к базе осуществляется через формат dsm. Итак, имя пользователя, тут может быть пароль, например, потом формат, по которому мы подключаемся — может быть TCP, может быть UNIX-сокет. Собственно, адрес сервера и имя базы данных. Теперь я указываю уже в параметрах — по сути, это query-параметры, charset-кодировка. И параметр interpolateParams, который отключит не prepare a statement, а будет сразу подставлять параметры в [НЕРАЗБОРЧИВО]. Окей, я создал строчку для подключения. Теперь я подключаюсь — обратите внимание, я не использую ни MySQL, ничего. Я использую функцию SQL из пакета database/sql, там использую Open. И вот тут уже я указываю MySQL, то есть я говорю, что мне, пожалуйста, подключись к этой базе данных, используя вот этот обработчик. Окей, подключились. Следующая строка: SetMaxOpenConns. Что это такое и зачем это надо? Дело в том, что MySQL имеет полностью синхронный протокол, то есть пока один запрос не отработал, вы не можете отправлять туда никакой другой. Для того чтобы в Go как-то с этим работать, отправлять много запросов, внутри там создается несколько подключений. И как раз количество этих подключений, оно регулируется вот этим методом. Теперь выполним пингование нашей базы данных, то есть подключимся. sql.Open — он не создает подключения сразу, он только инициализирует сам объект, а уже db.Ping уже реально подключается к базе данных. Поэтому если там случится ошибка, она случится вот здесь. Например, логин-пароль неправильный, либо база недоступна. Так, идем далее. Я использовал Gorilla mux-роутер, для того чтобы удобно обрабатывать get, post, delete-запросы. Я создаю handler, туда передаю базу данных, туда передаю шаблоны, с которыми буду работать, чтобы не использовать глобальные переменные — у меня в рамках одного вот хендлера. Отлично. Регистрация хендлеров — тут ничего сложного нету, какие-то методы доступны через get, какие-то — через post, и один — через delete. Можно было бы обойтись меньшим количеством, просто зарегистрировав один метод для любого типа, но в этом примере будем использовать прямо по отдельности. Хорошо. Рассмотрим список. Вот у меня есть несколько элементом, есть поле edited. Как его вывести, как вообще сделать select в базу? Начинаем, смотрим. Для начала я создаю слайс своих объектов для общего списка. Теперь, смотрите: я обращаюсь к объекту DB в моем хендлере — вот он объект, DB вот здесь. И я выполняю метод Query, он принимает в себя первым аргументом SQL-запрос, в котором могут быть плейсхолдеры, и следующими аргументами — уже значения для плейсхолдеров. В данном случае этого нету, мы это посмотрим позже. Так, отлично, нам вернулись наши строки, теперь мы по ним проитерируемся. И теперь, чтобы распаковать из бинарного формата MySQL, у rows, моего объекта, есть функция Scan. В Scan вам надо передать ваши параметры, в которые будет распаковано это значение. Соответственно, значение должно быть подходящего типа. Id — это int, Title — это string, а вот Updated, Updated — он у нас имеет тип NullString. Зачем это надо? Я вам напомню, что поле Updated у нас может иметь внутри, то есть там может не прийти значение. Поэтому, когда Scan попытается его распаковать, там будет внутри nil. И если я nil попробую присвоить в строку, будет ошибка. Соответственно, нужен способ, каким образом можно проверять, есть ли там значение NULL или нет. В пакете database/sql есть NullString, NullInt и еще несколько полей, которые имеют поле Valid, в котором находится булевый флаг — там NULL или не NULL, — и уже String, который нам будет говорить, какое там реальное значение. Можно обойтись другим способом, можно сделать вот так, и тогда вы тоже сможете его успешно распаковать, потому что если там будет nil, оно спокойно в nil присвоится и тут будет nil. Все честно. Итак, хорошо. Мы распаковали, ошибки у нас нет, добавили в общий список. В данном случае в данном примере я пользуюсь небольшим хелпером, который у меня паникует на каждую ошибку, но это сделано просто для упрощения кода. Вы так не делайте, обрабатывайте все ошибки нормально. Хорошо. Теперь, когда я полностью вычитал все записи из из запроса своего, мне обязательно нужно его закрыть. Если мы не будем его закрывать, то начнет течь. Поэтому всегда про это думайте. Окей, ну и дальше я делаю expand шаблона, для того чтобы показать вам это. Собственно, вот результат. Обратите внимание: edited есть у одного поля, у поста № 2, вот этого — edited нету, там на самом деле внутри находится NULL. То есть поле Valid у у String — оно выставлено на false, я его не вывожу. С NULL-полями, конечно, немножко замороченно работать. Вот простой SQL-запрос, который мы вычитываем. Конечно, вы можете использовать не не поля структуры, а какие-то временные переменные, которые вы можете создавать прямо здесь, но это не очень важно. Вот. Теперь попробуем добавить запись: New, и у меня есть два поля для ввода. Например, test, example description. Нажимаем Submit, поле у нас появилось за номером 3. Рассмотрим, как это происходит. Просто пока с формой тут работы с SQL нет, я просто сразу Отлично, теперь как сделать insert. Через функцию query не получится, она возвращает нам данные. Для работы с insert, update есть функция exec. Я туда передаю, собственно, мой sql-запрос и передаю прямо сразу, как пришло несколько значений. Обратите внимание, я использую placeholder, знак вопроса. Значение туда будет сразу поставлено с нужным эскейпингом. В данном примере я не делаю никакой валидации, нет ни авторизации, защиты от серф-токенов, вам они, конечно же, в продакшене будут нужны. Итак, exec, поле для insert, поле раз — вот для этого placeholder, поле два — вот для этого placeholder. Идем прямо из формы, то что нам пришло. Окей, RowsAffected — это количество записей, с которыми было какое-то обновление, LastInsertId сразу можно выгрести из result, и сразу мы получим номер вставленной записи, ну а дальше я делаю redirect. То есть ничего сложного, никакой магии. Теперь редактирование записи. Допустим, у меня есть тест, я жму edit и попробую сделать что-нибудь такое. [БЕЗ_ЗВУКА] Я попробую ввести туда какой-то тег и посмотрим, что получится. Нажимаем submit, и обратите внимание — у нас это не превратилось в тег. Почему так? Template html автоматически делает эскейпинг, это шаблонизатор, который учитывает контекст, в котором вы вводите переменную, поэтому в данном случае, если бы я использовал текст html, тут был бы тег, и было бы подсвечено жирным. Ну хорошо, я обновил запись, то есть я выбрал какую-то запись, мне нужно выбрать сначала ее из базы, чтобы подставить форму. Как выбирается? Да все просто. Обратите внимание на url items 3, то есть у меня прямо параметры использованы через переменные роутера, получаю ее через mux.Vars и конвертирую в id, ну так, на всякий случай. Выбираю запись, используя уже не query, а QueryRow. QueryRow сам за меня выберет эту запись, закроет connect, все хорошо, то есть QueryRow нужен для работы с единичными записями. Я перечислил поля, я указал один placeholder и, собственно, значение для этого placeholder. Отсканировал их все в переменную моей структуры и передал ее уже в форму. Опять-таки стандартный sql. Теперь update. Когда я нажимаю форму submit, что-то происходит. В апдейте я тоже конструирую sql-запрос: update, title, description updated. Я точно так же беру эти значения сразу из формы, никакой валидации, ничего. Не делайте так. Description, и я для каждого поля прописываю updates на какое-то статичное значение. Допустим, здесь мог бы подставляться пользователь, от имени которого был update. Ну и последним параметром идет Id вот это вот, то есть я говорю, у какой записи я хочу это удалить. Отлично, запись прошла, ошибки обработали, и в результате у меня есть RowsAffected, то есть сколько записей было изменено этим запросом. Ну и опять redirect, ничего супернового. Передали в функцию sql-запрос и параметры для этого sql-запроса. Удаление. В удалении тоже все просто. Передали sql-запрос и один параметр. Обратите внимание, я нигде не использую ручное добавление, конкатенацию самих параметров в базу данных. Всегда пользуйтесь placeholder. Если вы попробуете через sprintf или еще как-то руками конструировать запросы, где-то вы это проглядите и будет sql-инъекция. Sql-инъекция — это неприятно, и это очень частая причина взломов ваших сайтов, поэтому placeholder, placeholder, placeholder. Удаление. Опять-таки RowsAffected, в данном случае этот конец вызывается через JSON, если я нажму на кнопку delete, он мне скажет да, я говорю да, и он у меня удаляет. Всё. Вот работа с sql. Как я сказал, для [НЕРАЗБОРЧИВО] там будут очень незначительные отличия, вы можете ознакомиться с примерами в файлах к уроку. Что можно сказать про работу с sql? Иногда, конечно, бывает неудобно руками распаковывать все это дело, и для этого есть средства. Их мы рассмотрим далее, мы посмотрим, каким образом можно использовать пакеты, которые представляют нам обертки для этого.