Генерация SQL-запросов в Go с помощью squirrel

2 марта 2020

В рамках поста Работа с PostgreSQL в языке Go при помощи pgx был написан микросервис, использующий SQL-запросы в виде обыкновенных строк. Безусловно, это единственный правильный способ работать с РСУБД, однако в некоторых задачах он может быть не очень удобен. Например, если вам нужно генерировать запросы со сложными WHERE-условиями в зависимости от пользовательского ввода. Одно из возможных решений заключается в использовании библиотеки squirrel.

Не будем ходить вокруг да около, и просто перепишем наши запросы на белочку.

SELECT-запрос:

    // ...
    sql, args, err := sq.Select("id", "name", "phone").
                        From("phonebook").
                        Where(sq.Eq{"id": id}).
                        PlaceholderFormat(sq.Dollar).
                        ToSql()
    if err != nil {
        log.Errorf("Unable to build SELECT query: %v", err)
        w.WriteHeader(500)
        return
    }
    log.Infof("Executing SQL: %s", sql)
    row := conn.QueryRow(context.Background(), sql, args...)
    // ...

INSERT-запрос:

    // ...
    sql, args, err := sq.Insert("phonebook").
                        Columns("name", "phone").
                        Values(rec.Name, rec.Phone).
                        Suffix("RETURNING id").
                        PlaceholderFormat(sq.Dollar).
                        ToSql()
    if err != nil {
        log.Errorf("Unable to build INSERT query: %v", err)
        w.WriteHeader(500)
        return
    }
    log.Infof("Executing SQL: %s", sql)
    row := conn.QueryRow(context.Background(), sql, args...)
    // ...

UPDATE:

    // ...
    sql, args, err :=  sq.Update("phonebook").
                        Set("name", rec.Name).
                        Set("phone",  rec.Phone).
                        Where(sq.Eq{"id": id}).
                        PlaceholderFormat(sq.Dollar).
                        ToSql()
    if err != nil {
        log.Errorf("Unable to build UPDATE query: %v", err)
        w.WriteHeader(500)
        return
    }
    log.Infof("Executing SQL: %s", sql)
    ct, err := conn.Exec(context.Background(), sql, args...)
    // ...

И, наконец, DELETE:

    // ...
    sql, args,  err := sq.Delete("phonebook").
                        Where(sq.Eq{"id": id}).
                        PlaceholderFormat(sq.Dollar).
                        ToSql()
    if err != nil {
        log.Errorf("Unable to build DELETE query: %v", err)
        w.WriteHeader(500)
        return
    }
    log.Infof("Executing SQL: %s", sql)
    ct, err := conn.Exec(context.Background(), sql, args...)
    // ...

Собираем проект, запускаем тесты, и проверяем, что ничего не сломалось. Также смотрим, что за запросы в итоге были сгенерированы:

SELECT id, name, phone FROM phonebook WHERE id = $1;
INSERT INTO phonebook (name,phone) VALUES ($1,$2) RETURNING id;
UPDATE phonebook SET name = $1, phone = $2 WHERE id = $3;
DELETE FROM phonebook WHERE id = $1;

Выглядит законно.

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

Метки: , .