Verification: a143cc29221c9be0

Mysql и php sql запрос

Mysql и php sql запрос

Конвертирование типов из MySQL в Go

Под капотом метода rows.Scan(), драйвер базы данных автоматически преобразует MySQL типы в типы языка программирования Go:

  • CHAR, VARCHAR и TEXT соответствуют типу string;
  • BOOLEAN соответствует bool;
  • INT соответствует int;
  • BIGINT соответствует int64;
  • DECIMAL и NUMERIC соответствуют float;
  • TIME, DATE и TIMESTAMP соответствуют time.Time.

Особенность нашего MySQL драйвера заключается в том, нам нужно использовать параметр parseTime=true в нашей строке подключения к MySQL, чтобы заставить его преобразовывать поля TIME и DATE в time.Time. В противном случае, он вернёт их как объекты []byte. Это один из многих предлагаемых параметров для предварительной настройки драйвера базы данных при подключении к нему.

Модели базы данных в обработчиках

Отлично, давайте воспользуемся методом SnippetModel.Get() на практике.

Откройте файл cmd/web/handlers.go и обновите обработчик showSnippet, чтобы он возвращал данные для определенной записи:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

package main

import (

    "errors" // новый импорт

    "fmt"

    "html/template"

    "net/http"

    "strconv"

    "golangify.com/snippetbox/pkg/models" // новый импорт

)

...

func (app *application) showSnippet(w http.ResponseWriter, r *http.Request) {

id, err := strconv.Atoi(r.URL.Query().Get("id"))

if err != nil || id 1 {

app.notFound(w) // Страница не найдена.

return

}

// Вызываем метода Get из модели Snipping для извлечения данных для

// конкретной записи на основе её ID. Если подходящей записи не найдено,

// то возвращается ответ 404 Not Found (Страница не найдена).

s, err := app.snippets.Get(id)

if err != nil {

if errors.Is(err, models.ErrNoRecord) {

app.notFound(w)

} else {

app.serverError(w, err)

}

return

}

// Отображаем весь вывод на странице.

fmt.Fprintf(w, "%v", s)

}

...

Запускаем наше веб-приложение из терминала:

Перейдите в браузере на страницу http://127.0.0.1:4000/snippet?id=1. Вы должны увидеть HTTP ответ, похожий на следующий:

SELECT запрос в Golang

Также, можете попробовать сделать несколько запросов для других заметок, срок действия которых истек или которых вообще не существуют (например, id=99), чтобы убедиться, что они возвращают ответ 404 Not Found:

MySQL запись не найдена

Обработка ошибок используя errors.Is()

В коде выше мы использовали функцию errors.Is(), которая была введена в Go 1.13, чтобы проверить, нужная ли нам ошибка выскочила (в нашем случае проверяется, если нам попалась именно ошибка sql.ErrNoRows).

Обсудим данный вопрос немного подробнее.

Во-первых, sql.ErrNoRows является примером так называемых предопределённых ошибок (по аналогии с Python, это будут «исключения» exceptions), которых мы можем приблизительно определить как объект error, хранящийся в глобальной переменной. Обычно они создаются с помощью функции errors.New(). Несколько примеров  ошибок из стандартной библиотеки — io.ErrUnexpectedEOF и bytes.ErrTooLarge.

Только что созданная нами ошибка models.ErrNoRecord является примером предопределённой ошибки.

У нас есть её «имя» и мы можем «ловить» её в случае если она возникнет при работе программы. Всё работает по принципу try - catch в Java, PHP или try — except в Python.

В старых версиях Go (до 1.13) лучший способ проверить, если ошибка совпадает с предопределённой ошибкой, выглядел бы так:

if err == sql.ErrNoRows {

    // Делает что-то

} else {

    // Делает что-то другое

}

Однако после Go 1.13 лучше использовать функцию errors.Is():

if errors.Is(err, sql.ErrNoRows) {

    // Делает что-то

} else {

    // Делает что-то другое

}

Причина в том, что в Go 1.13 появилась возможность оборачивать ошибки для добавления дополнительной информации. По сути, мы создаём новую ошибку на базе уже существующей ошибки.

Для строго стиля проверки на ошибку — предопределённая ошибка и созданная (обёрнутая) на основе неё другая новая ошибка — не будут совпадать, так как обернутая ошибка не равна оригинальной предопределённой ошибке.

Функция errors.Is() наоборот работает путем распаковки ошибок — при необходимости — перед проверкой на совпадения.

Если у вас Go 1.13 или новее, лучше использовать error.Is(). Это хороший способ защитить код в будущем и предотвратить проблемы, вызванные вами — или любыми пакетами, которые ваш код импортирует.

Существует также другая функция, errors.As(), которую вы можете использовать, чтобы проверить, есть ли (у потенциально обернутой — wrapped) ошибки определенный тип. Мы будем использовать эту функцию в будущих уроках.

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

Метод для получения данных записи

Мы специально сделали код в SnippetModel.Get() немного длиннее, чтобы было проще понять, что именно происходит за кулисами вашего кода.

На практике, можно сократить код (или хотя бы количество строк), воспользовавшись тем, что появление ошибки из DB.QueryRow() откладываются до вызова Scan(). Функциональной разницы нет, так что если вы хотите, можете переписать код следующим образом:

func (m *SnippetModel) Get(id int) (*models.Snippet, error) {

    s := &models.Snippet{}

    err := m.DB.QueryRow("SELECT ...", id).Scan(&s.ID, &s.Title, &s.Content, &s.Created, &s.Expires)

    if err != nil {

        if errors.Is(err, sql.ErrNoRows) {

            return nil, models.ErrNoRecord

        } else {

             return nil, err

        }

    }

    return s, nil

}

Сырой запрос (Raw Query)

У Laravel для работы с базой данных есть механизм под названием Eloquent, плюс имеется мощный конструктор запросов (Query Builder), но иногда необходимо написать просто «сырой» запрос (Raw Query). Это можно сделать с помощью фасада Illuminate\Support\Facades\DB, который имеет методы для каждого типа запроса: select, update, insert, delete и statement.

1. Выполнение запроса SELECT

Метод фасада select() позволяет выполнить SELECT запрос:

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;

class UserController extends Controller {
    /**
     * Показать список всех пользователей
     *
     * @return \Illuminate\Http\Response
     */
    public function index() {
        $users = DB::select('select * from users where admin = ?', [0]);
        return view('user.index', ['users' => $users]);
    }
}

Первый аргумент метода select() — сырой SQL-запрос, второй — значения параметров для прикрепления к запросу. Обычно это значения для формирования условия WHERE. Привязка параметров обеспечивает защиту от SQL-инъекций. Метод select()возвращает массив объектов stdClass.

foreach ($users as $user) {
    echo $user->name;
}

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

$user = DB::select('select * from users where id = :id', ['id' => 1]);

2. Выполнение запроса INSERT

Метод фасада insert() позволяет выполнить INSERT запрос:

DB::insert('insert into users (id, name, email) values (?, ?, ?)', [1, 'Сергей Иванов', 'ivanov.s@mail.ru']);
DB::insert(
    'insert into users (id, name, email) values (:id, :name, :email)',
    ['id' => 1, 'name' => 'Сергей Иванов',  'email' => 'ivanov.s@mail.ru']
);

3. Выполнение запроса UPDATE

Для обновления существующих записей используется метод update(), который возвращает количество изменённых записей:

$affected = DB::update('update users set votes = :votes where id = :id', ['id' => 1, 'votes' => 100]);

4. Выполнение запроса DELETE

Для удаления записей из БД используется метод delete(), который возвращает количество изменённых записей:

$deleted = DB::delete('delete from users where id = :id', ['id' => 1]);

5. Выполнение запроса общего типа

Некоторые запросы к БД не возвращают никаких значений. Для операций такого типа предназначен метод statement() фасада:

DB::statement('drop table users');

6. Транзакции

Для выполнения набора запросов внутри одной транзакции предназначен метод transaction(). Если в функции-замыкании произойдёт исключение, транзакция автоматически откатится. А если функция выполнится успешно, транзакция автоматически применится.

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);
    DB::table('posts')->delete();
});

Метод transaction() принимает второй необязательный аргумент, с помощью которого задаётся число повторных попыток транзакции при возникновении взаимной блокировки. После истечения этих попыток будет выброшено исключение:

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);
    DB::table('posts')->delete();
}, 5);

Чтобы запустить транзакцию вручную и иметь полный контроль над её откатом и применением:

DB::beginTransaction();

Можно откатить транзакцию методом rollBack():

DB::rollBack();

Можно применить транзакцию методом commit():

DB::commit();