Показаны сообщения с ярлыком работа с базами данных в Golang. Показать все сообщения
Показаны сообщения с ярлыком работа с базами данных в Golang. Показать все сообщения

четверг, 9 сентября 2021 г.

Как избежать риска внедрения SQL-кода с пакетом database/sql в Golang

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

Код в следующем примере использует символ ? символ в качестве заполнителя для параметра id, который предоставляется как аргумент функции:

// Правильный формат для выполнения оператора SQL с параметрами.
rows, err := db.Query("SELECT * FROM user WHERE id = ?", id)

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

Примечание. Заполнители параметров различаются в зависимости от СУБД и используемого драйвера. Например, драйвер pq для Postgres принимает форму-заполнитель, такую ​​как $1 вместо ?.

У вас может возникнуть соблазн использовать функцию из пакета fmt для сборки оператора SQL в виде строки с включенными параметрами - например:

// SECURITY RISK!
rows, err := db.Query(fmt.Sprintf("SELECT * FROM user WHERE id = %s", id))

Это небезопасно! Когда вы это делаете, Go собирает весь оператор SQL, заменяя глагол формата %s значением параметра перед отправкой полного оператора в СУБД. Это создает риск внедрения SQL-кода, поскольку вызывающий код может отправить неожиданный фрагмент SQL в качестве аргумента id. Этот фрагмент может завершить инструкцию SQL непредсказуемым образом, что опасно для вашего приложения.

Например, передав определенное значение %s, вы можете получить что-то вроде следующего, которое может вернуть все пользовательские записи в вашей базе данных:

SELECT * FROM user WHERE id = 1 OR 1=1;


Читайте также:


среда, 8 сентября 2021 г.

Управление подключениями к базе данных с пакетом database/sql в Golang

Для подавляющего большинства программ вам не нужно настраивать параметры пула соединений sql.DB по умолчанию. Но для некоторых продвинутых программ вам может потребоваться настроить параметры пула соединений или явно работать с соединениями. В этом посте объясняется, как это сделать.

Дескриптор базы данных sql.DB безопасен для одновременного использования несколькими горутинами (это означает, что дескриптор - это то, что другие языки могут назвать "потокобезопасным"). Некоторые другие библиотеки доступа к базе данных основаны на соединениях, которые могут использоваться только для одной операции за раз. Чтобы восполнить этот пробел, каждый sql.DB управляет пулом активных подключений к базе данных, создавая новые по мере необходимости для параллелизма в вашей программе Go.

Пул соединений подходит для большинства нужд доступа к данным. Когда вы вызываете метод sql.DB Query или Exec, реализация sql.DB извлекает доступное соединение из пула или, при необходимости, создает его. Пакет возвращает соединение с пулом, когда оно больше не нужно. Это поддерживает высокий уровень параллелизма для доступа к базе данных.

Настройка свойств пула соединений

Вы можете установить свойства, которые определяют, как пакет sql управляет пулом соединений. Чтобы получить статистику о влиянии этих свойств, используйте DB.Stats.

Установка максимального количества открытых подключений

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

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

Установка максимального количества неактивных подключений

DB.SetMaxIdleConns изменяет ограничение на максимальное количество неактивных соединений, поддерживаемых sql.DB.

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

Установка максимального количества времени, в течение которого соединение может простаивать

DB.SetConnMaxIdleTime устанавливает максимальное время простоя соединения перед его закрытием. Это приводит к тому, что sql.DB закрывает соединения, которые не использовались дольше заданного времени.

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

Установка максимального срока службы подключений

Использование DB.SetConnMaxLifetime устанавливает максимальное время, в течение которого соединение может оставаться открытым перед закрытием.

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

Использование выделенных соединений

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

Наиболее распространенный пример - это транзакции, которые обычно начинаются с команды BEGIN, заканчиваются командой COMMIT или ROLLBACK и включают все команды, выдаваемые при соединении между этими командами в общей транзакции. В этом случае используйте поддержку транзакций пакета sql.

Для других случаев использования, когда последовательность отдельных операций должна выполняться в одном и том же соединении, пакет sql предоставляет выделенные соединения. DB.Conn получает выделенное соединение sql.Conn. В sql.Conn есть методы BeginTx, ExecContext, PingContext, PrepareContext, QueryContext и QueryRowContext, которые ведут себя как эквивалентные методы в БД, но используют только выделенное соединение. По завершении выделенного соединения ваш код должен освободить его с помощью Conn.Close.


Читайте также:


Купить gopher

вторник, 7 сентября 2021 г.

Отмена незавершенных операций с базой данных в Golang

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

Например, вы можете захотеть:

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

Многие API-интерфейсы для разработчиков Go включают методы, которые принимают аргумент Context, что упрощает использование контекста во всем приложении.

Отмена операций с базой данных после таймаута

Вы можете использовать контекст, чтобы установить таймаут или крайний срок, по истечении которого операция будет отменена. Чтобы получить контекст с таймаутом или крайним сроком, вызовите context.WithTimeout или context.WithDeadline.

Код в следующем примере таймаута извлекает контекст и передает его в метод sql.DB QueryContext.

func QueryWithTimeout(ctx context.Context) {
    // Создаем Context с таймаутом.
    queryCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // Передаем Context с таймаутом с запросом.
    rows, err := db.QueryContext(queryCtx, "SELECT * FROM album")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    // Обрабатываем возвращенные строки.
}

Когда один контекст является производным от внешнего контекста, поскольку queryCtx является производным от ctx в этом примере, если внешний контекст отменяется, то производный контекст также автоматически отменяется. Например, в HTTP-серверах метод http.Request.Context возвращает контекст, связанный с запросом. Этот контекст отменяется, если HTTP-клиент отключает или отменяет HTTP-запрос (возможно в HTTP/2). Передача контекста HTTP-запроса в QueryWithTimeout выше приведет к преждевременной остановке запроса к базе данных либо в том случае, если общий HTTP-запрос был отменен, либо если запрос занял более пяти секунд.

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


Читайте также:


Купить gopher

понедельник, 6 сентября 2021 г.

Выполнение транзакций с пакетом database/sql в Golang

Вы можете выполнять транзакции базы данных, используя sql.Tx, который представляет транзакцию. Помимо методов Commit и Rollback, представляющих семантику конкретной транзакции, sql.Tx имеет все методы, которые вы можете использовать для выполнения общих операций с базой данных. Чтобы получить sql.Tx, вызовите DB.Begin или DB.BeginTx.

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

  1. Начало транзакции.
  2. Выполнение набора операций с базой данных.
  3. Если ошибки не возникает, транзакция фиксируется для внесения изменений в базу данных.
  4. Если возникает ошибка, откат транзакции, чтобы оставить базу данных без изменений.

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

Начните транзакцию.

DB.Begin или DB.BeginTx начинают новую транзакцию базы данных, возвращая sql.Tx, который ее представляет.

Выполните операции с базой данных.

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

  • Exec и ExecContext для внесения изменений в базу данных с помощью операторов SQL, таких как INSERT, UPDATE и DELETE.
  • Query, QueryContext, QueryRow и QueryRowContext для операций, возвращающих строки.
  • Prepare, PrepareContext, Stmt и StmtContext для предварительного определения подготовленных операторов.

Завершите транзакцию одним из следующих способов:

  • Зафиксируйте транзакцию с помощью Tx.Commit.
    Если фиксация завершается успешно (возвращает nil ошибку), то все результаты запроса подтверждаются как действительные, и все выполненные обновления применяются к базе данных как одно атомарное изменение. В случае сбоя фиксации все результаты Query и Exec на Tx должны быть отклонены как недопустимые.
  • Откатите транзакцию с помощью Tx.Rollback.
    Даже если Tx.Rollback завершится неудачно, транзакция больше не будет действительной и не будет зафиксирована в базе данных.

Лучшие практики

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

  • Используйте API, описанные в этом разделе, для управления транзакциями. Не используйте операторы SQL, связанные с транзакциями, такие как BEGIN и COMMIT, напрямую - это может привести к непредсказуемому состоянию вашей базы данных, особенно в конкурентных программах.
  • При использовании транзакции старайтесь не вызывать напрямую методы sql.DB, не связанные с транзакцией, так как они будут выполняться вне транзакции, давая вашему коду непоследовательное представление о состоянии базы данных или даже вызывая взаимоблокировки.

Пример использования транзакции

Код в следующем примере использует транзакцию для создания нового заказа клиента на альбом. В ходе этого код будет:

  • Начинать транзакцию.
  • Откладывать (defer) откат транзакции. Если транзакция завершится успешно, она будет зафиксирована до выхода из функции, что сделает отложенный откатный вызов бездействующим. Если транзакция завершится неудачно, она не будет зафиксирована, а это означает, что откат будет вызываться при выходе из функции.
  • Убедится, что для альбома, который заказывает клиент, имеется достаточное количество штук.
  • Если товара достаточно, обновит запас, уменьшив его на количество заказанных альбомов.
  • Создаст новый заказ и получит сгенерированный идентификатор нового заказа для клиента.
  • Зафиксирует транзакцию и вернет идентификатор.

В этом примере используются методы Tx, которые принимают аргумент context.Context. Это позволяет отменить выполнение функции, включая операции с базой данных, если она выполняется слишком долго или закрывается клиентское соединение.

// CreateOrder создает заказ для альбома 
// и возвращает идентификатор нового заказа.
func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {

    // Создаем вспомогательную функцию 
    // для подготовки результатов сбоя.
    fail := func(err error) (int64, error) {
        return fmt.Errorf("CreateOrder: %v", err)
    }

    // Получаем Tx для выполнения запросов на транзакцию.
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fail(err)
    }
    // Откладываем откат на случай, если что-то не удастся.
    defer tx.Rollback()

    // Подтверждаем, что количество копий альбома 
    // достаточно для заказа.
    var enough bool
    if err = tx.QueryRowContext(ctx, "SELECT (quantity >= ?) from album where id = ?",
        quantity, albumID).Scan(&enough); err != nil {
        if err == sql.ErrNoRows {
            return fail(fmt.Errorf("no such album"))
        }
        return fail(err)
    }
    if !enough {
        return fail(fmt.Errorf("not enough inventory"))
    }

    // Обновляем запас альбома, чтобы удалить количество в заказе.
    _, err = tx.ExecContext(ctx, "UPDATE album SET quantity = quantity - ? WHERE id = ?",
        quantity, albumID)
    if err != nil {
        return fail(err)
    }

    // Создаем новую строку в таблице album_order.
    result, err := tx.ExecContext(ctx, "INSERT INTO album_order (album_id, cust_id, quantity, date) VALUES (?, ?, ?, ?)",
        albumID, custID, quantity, time.Now())
    if err != nil {
        return fail(err)
    }
    // Получаем идентификатор только что созданной позиции заказа.
    orderID, err := result.LastInsertId()
    if err != nil {
        return fail(err)
    }

    // Фиксируем транзакцию.
    if err = tx.Commit(); err != nil {
        return fail(err)
    }

    // Возвращаем идентификатор заказа.
    return orderID, nil
}


Читайте также:


купить игрушку gopher

воскресенье, 5 сентября 2021 г.

Использование подготовленных операторов с пакетом database/sql в Golang

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

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от СУБД и драйвера, который вы используете. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

Что такое подготовленный оператор?

Подготовленный оператор - это SQL, который анализируется и сохраняется СУБД, обычно содержащий заполнители, но без фактических значений параметров. Позже оператор может быть выполнен с набором значений параметров.

Как использовать подготовленный оператор

Если вы планируете выполнять один и тот же SQL несколько раз, вы можете использовать sql.Stmt, чтобы заранее подготовить оператор SQL, а затем выполнить его по мере необходимости.

В следующем примере создается подготовленный оператор, который выбирает конкретный альбом из базы данных. DB.Prepare возвращает sql.Stmt, представляющий подготовленный оператор для данного текста SQL. Вы можете передать параметры для оператора SQL в Stmt.Exec, Stmt.QueryRow или Stmt.Query для запуска оператора.

// AlbumByID получает указанный альбом.
func AlbumByID(id int) (Album, error) {
    // Определяем подготовленный оператор. 
    // Обычно стоит определять оператор
    // в другом месте и сохранять его 
    // для использования в таких функциях, как эта.
    stmt, err := db.Prepare("SELECT * FROM album WHERE id = ?")
    if err != nil {
        log.Fatal(err)
    }

    var album Album

    // Выполняем подготовленный оператор, 
    // передавая значение id для параметра, 
    // заполнитель которого - ?
    err := stmt.QueryRow(id).Scan(&album.ID, &album.Title, 
        &album.Artist, &album.Price, &album.Quantity)
    if err != nil {
        if err == sql.ErrNoRows {
            // Обработка случая отсутствия возвращенных строк.
        }
        return album, err
    }
    return album, nil
}

Поведение подготовленного оператора

Подготовленный sql.Stmt предоставляет обычные методы Exec, QueryRow и Query для вызова оператора.

Однако, поскольку sql.Stmt уже представляет предустановленный оператор SQL, его методы Exec, QueryRow и Query принимают только значения параметров SQL, соответствующие заполнителям, опуская текст SQL.

Вы можете определить новый sql.Stmt по-разному, в зависимости от того, как вы его будете использовать.

  • DB.Prepare и DB.PrepareContext создают подготовленный оператор, который может выполняться изолированно, вне транзакции, как это делают DB.Exec и DB.Query.
  • Tx.Prepare, Tx.PrepareContext, Tx.Stmt и Tx.StmtContext создают подготовленный оператор для использования в конкретной транзакции. Prepare и PrepareContext используют текст SQL для определения оператора. Stmt и StmtContext используют результат DB.Prepare или DB.PrepareContext. То есть они преобразуют sql.Stmt не для транзакций в sql.Stmt для этой транзакции.
  • Conn.PrepareContext создает подготовленный оператор из sql.Conn, который представляет зарезервированное соединение.

Убедитесь, что stmt.Close вызывается, когда ваш код завершает работу с оператором. Это освободит все ресурсы базы данных (например, подлежащие соединения), которые могут быть с ней связаны. Для операторов, которые являются только локальными переменными в функции, достаточно defer stmt.Close().

Функции для создания подготовленного оператора

DB.Prepare, DB.PrepareContext - Подготовливает оператор для изолированного выполнения, иначе он будет преобразован в подготовленный оператор внутри транзакции с помощью Tx.Stmt.

Tx.Prepare, Tx.PrepareContext, Tx.Stmt, Tx.StmtContext - Подготавливает оператор для использования в конкретной транзакции.

Conn.PrepareContext - Для использования с зарезервированными соединениями.


Читайте также:


Купить gopher

суббота, 4 сентября 2021 г.

Запрос данных из базы данных с пакетом database/sql в Golang

При выполнении оператора SQL, который возвращает данные, используйте один из Query методов, предоставленных в пакете database/sql. Каждый из них возвращает Row или Rows, данные которых можно скопировать в переменные с помощью Scan метода. Вы можете использовать эти методы, например, для выполнения операторов SELECT.

При выполнении оператора, который не возвращает данные, вместо этого можно использовать метод Exec или ExecContext.

Пакет database/sql предоставляет два способа выполнить запрос, возвращающий данные.

  • Запрос одной строки - QueryRow возвращает не более одной строки из базы данных.
  • Запрос нескольких строк - Query возвращает все соответствующие запросу строки в виде структуры Rows, которую ваш код может пройти в цикле.

Если ваш код будет многократно выполнять один и тот же оператор SQL, рассмотрите возможность использования подготовленного оператора.

Внимание: не используйте функции форматирования строк, такие как fmt.Sprintf, для сборки оператора SQL! Вы можете создать риск внедрения SQL-кода.

Запрос одной строки

QueryRow извлекает не более одной строки базы данных, например, когда вы хотите найти данные по уникальному идентификатору. Если запрос возвращает несколько строк, метод Scan отбрасывает все, кроме первой.

QueryRowContext работает как QueryRow, но с аргументом context.Context.

В следующем примере используется запрос, чтобы узнать, присуствует ли в наличии достаточное количество товара для совершения покупки. Оператор SQL возвращает true, если товара достаточно, и false, если нет. Row.Scan копирует логическое возвращаемое значение в переменную enough через указатель.

func canPurchase(id int, quantity int) (bool, error) {
    var enough bool
    // Запрос значения на основе одной строки.
    if err := db.QueryRow("SELECT (quantity >= ?) from album where id = ?",
        quantity, id).Scan(&enough); err != nil {
        if err == sql.ErrNoRows {
            return false, fmt.Errorf("canPurchase %d: unknown album", id)
        }
        return false, fmt.Errorf("canPurchase %d: %v", id)
    }
    return enough, nil
}

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от используемой СУБД и драйвера. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

Обработка ошибок

Сам QueryRow не возвращает ошибок. Вместо этого Scan сообщает о любых ошибках при комбинированном поиске и сканировании. Он возвращает sql.ErrNoRows, если запрос не находит строк.

Функции для возврата одной строки

DB.QueryRow, DB.QueryRowContext - Выполнение однострочного запроса изолированно.

Tx.QueryRow, Tx.QueryRowContext - Выполнение однострочного запроса внутри более крупной транзакции.

Stmt.QueryRow, Stmt.QueryRowContext - Выполнить однострочный запрос с использованием уже подготовленного оператора.

Conn.QueryRowContext - Для использования с зарезервированными соединениями.

Запрос нескольких строк

Вы можете запросить несколько строк с помощью Query или QueryContext, которые возвращают Rows, представляющие результаты запроса. Ваш код выполняет итерацию по возвращенным строкам, используя Rows.Next. Каждая итерация вызывает Scan для копирования значений столбцов в переменные.

QueryContext работает как Query, но с аргументом context.Context.

В следующем примере выполняется запрос для возврата альбомов указанного исполнителя. Альбомы возвращаются в виде sql.Rows. Код использует Rows.Scan для копирования значений столбцов в переменные, представленные указателями.

func albumsByArtist(artist string) ([]Album, error) {
    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    // Срез альбомов для хранения данных из возвращенных строк.
    var albums []Album

    // Цикл по строкам, 
    // используя Scan для назначения данных столбца полям структуры.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist,
            &alb.Price, &alb.Quantity); err != nil {
            return albums, err
        }
        albums = append(albums, album)
    }
    if err = rows.Err(); err != nil {
        return albums, err
    }
    return albums, nil
}

Обратите внимание на отложенный вызов rows.Close. Он освобождает любые ресурсы, удерживаемые rows, независимо от того, что функция возвращает. Цикл по всем строкам также неявно закрывает rows, но лучше использовать defer, чтобы убедиться, что rows закрыты, несмотря ни на что.

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от СУБД и драйвера, который вы используете. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

Обработка ошибок

Обязательно проверьте наличие ошибки в sql.Rows проходя в цикле по результатам запроса. Если запрос не удался, ваш код узнает об этом именно так.

Функции для возврата нескольких строк

DB.Query, DB.QueryContext - Выполнение запроса изолированно.

Tx.Query, Tx.QueryContext - Выполнить запрос внутри более крупной транзакции.

Stmt.Query, Stmt.QueryContext - Выполнить запрос, используя уже подготовленный оператор.

Conn.QueryContext - Для использования с зарезервированными соединениями.

Обработка значений столбца, допускающего значение NULL

Пакет database/sql предоставляет несколько специальных типов, которые вы можете использовать в качестве аргументов для функции Scan, когда значение столбца может быть null. Каждый из них включает поле Valid, которое сообщает, является ли значение ненулевым, и поле, содержащее значение, если это так.

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

var s sql.NullString
err := db.QueryRow("SELECT name FROM customer WHERE id = ?", id).Scan(&s)
if err != nil {
    log.Fatal(err)
}

// Находим имя клиента, используя заполнитель, если он отсутствует.
name := "Valued Customer"
if s.Valid {
    name = s.String
}

Вот некоторые типы, предназначенные для полей, имеющих null значения:

  • NullBool
  • NullFloat64
  • NullInt32
  • NullInt64
  • NullString
  • NullTime

Получение данных из столбцов

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

Существует базовый набор преобразований данных, поддерживаемых всеми драйверами, например преобразование SQL INT в Go int.

Как и следовало ожидать, Scan преобразует типы столбцов в похожие типы Go. Например, Scan преобразует из SQL CHAR, VARCHAR и TEXT в строку Go. Однако Scan также выполнит преобразование в другой тип Go, который хорошо подходит для значения столбца. Например, если столбец представляет собой VARCHAR, который всегда будет содержать число, вы можете указать числовой тип Go, например int, для получения значения, и Scan преобразует его с помощью strconv.Atoi за вас.

Обработка нескольких наборов результатов

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

Rows.NextResultSet подготавливает следующий набор результатов, так что вызов Rows.Next извлекает первую строку из этого следующего набора. Он возвращает логическое значение, указывающее, существует ли вообще следующий набор результатов.

Код в следующем примере использует DB.Query для выполнения двух операторов SQL. Первый набор результатов получается из первого запроса в процедуре, извлекающего все строки в таблице album. Следующий набор результатов - из второго запроса, извлекающего строки из таблицы song.

rows, err := db.Query("SELECT * from album; SELECT * from song;")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// Проходим в цикле по первому набору результатов.
for rows.Next() {
    // Обрабатываем набор результатов.
}

// Переход к следующему набору результатов.
rows.NextResultSet()

// Проходим в цикле по второму набору результатов.
for rows.Next() {
    // Обрабатываем второй набор результатов.
}

// Проверяем наличие ошибок в любом наборе результатов.
if err := rows.Err(); err != nil {
    log.Fatal(err)
}


Читайте также:


Купить gopher

четверг, 2 сентября 2021 г.

Выполнение операторов SQL, которые не возвращают данные, с пакетом database/sql в Golang

Когда вы выполняете действия с базой данных, которые не возвращают данные, используйте метод Exec или ExecContext из пакета database/sql. Операторы SQL, которые вы выполняете таким образом, включают INSERT, DELETE и UPDATE.

Если ваш запрос может возвращать строки, используйте вместо этого метод Query или QueryContext.

Метод ExecContext работает так же, как метод Exec, но с дополнительным аргументом context.Context.

Код в следующем примере использует DB.Exec для выполнения инструкции по добавлению нового альбома в таблицу album.

func AddAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist) VALUES (?, ?)", alb.Title, alb.Artist)
    if err != nil {
        return 0, fmt.Errorf("AddAlbum: %v", err)
    }

    // Получить сгенерированный идентификатор нового альбома для клиента.
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("AddAlbum: %v", err)
    }
    // Возвращаем ID нового альбома.
    return id, nil
}

DB.Exec возвращает значения: sql.Result и ошибку. Когда ошибка равна nil, вы можете использовать Result для получения идентификатора последнего вставленного элемента (как в примере) или для получения количества строк, затронутых операцией.

Примечание. Заполнители параметров в подготовленных операторах различаются в зависимости от СУБД и драйвера, который вы используете. Например, драйвер pq для Postgres требует заполнителя типа $1 вместо ?.

Если ваш код будет многократно выполнять один и тот же оператор SQL, рассмотрите возможность использования sql.Stmt для создания повторно используемого подготовленного оператора из оператора SQL.

Внимание: не используйте функции форматирования строк, такие как fmt.Sprintf, для сборки оператора SQL! Вы можете создать риск внедрения SQL-кода (SQL-injection).

Функции для выполнения операторов SQL, которые не возвращают строки

DB.Exec, DB.ExecContext - Выполнение одного оператора SQL изолированно.

Tx.Exec, Tx.ExecContext - Выполнение инструкции SQL в более крупной транзакции.

Stmt.Exec, Stmt.ExecContext - Выполнение уже подготовленного оператора SQL.

Conn.ExecContext - Для использования с зарезервированными соединениями.


Читайте также:


Купить gopher

среда, 1 сентября 2021 г.

Открытие дескриптора базы данных с Golang

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

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

В дополнение к API, доступным в пакете database/sql, сообщество Go разработало драйверы для всех наиболее распространенных (и многих необычных) систем управления базами данных (СУБД).

При открытии дескриптора базы данных вы выполняете следующие высокоуровневые шаги:

  1. Находите драйвер.
    Драйвер переводит запросы и ответы между вашим кодом Go и базой данных.
  2. Открываете дескриптор базы данных.
    После того, как вы импортировали драйвер, вы можете открыть дескриптор конкретной базы данных.
  3. Подтверждаете соединение.
    После того, как вы открыли дескриптор базы данных, ваш код может проверить, доступно ли соединение.

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

Поиск и импорт драйвера базы данных

Вам понадобится драйвер базы данных, поддерживающий СУБД, которую вы используете. Чтобы найти драйвер для своей базы данных, см. SQLDrivers.

Чтобы сделать драйвер доступным для вашего кода, вы импортируете его, как любой другой пакет Go. Например:

import "github.com/go-sql-driver/mysql"

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

import _ "github.com/go-sql-driver/mysql"

Примечание. Рекомендуется избегать использования собственного API драйвера базы данных для операций с базой данных. Вместо этого используйте функции из пакета database/sql. Это поможет сохранить слабую связанность вашего кода с СУБД, облегчая при необходимости переключение на другую СУБД.

Открытие дескриптора базы данных

Дескриптор базы данных sql.DB обеспечивает возможность чтения и записи в базу данных по отдельности или в транзакции.

Вы можете получить дескриптор базы данных, вызвав либо sql.Open (который принимает строку подключения), либо sql.OpenDB (который принимает driver.Connector). Оба возвращают указатель на sql.DB.

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

Открытие с помощью строки подключения

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

Вот пример для MySQL:

db, err = sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
if err != nil {
    log.Fatal(err)
}

Однако вы, вероятно, обнаружите, что более структурированный сбор параметров соединения дает вам более читаемый код. Подробности зависят от драйвера.

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

// Указываем параметры подключения.
cfg := mysql.Config{
    User:   username,
    Passwd: password,
    Net:    "tcp",
    Addr:   "127.0.0.1:3306",
    DBName: "jazzrecords",
}

// Получаем дескриптор базы данных.
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
    log.Fatal(err)
}

Открытие с помощью коннектора

Используйте функцию sql.OpenDB, если вы хотите воспользоваться специфичными для драйвера функциями подключения, которые недоступны в строке подключения. Каждый драйвер поддерживает свой собственный набор параметров подключения, часто предоставляя способы настройки запроса подключения, специфичного для СУБД.

Адаптировав предыдущий пример sql.Open для использования sql.OpenDB, вы можете создать дескриптор с таким кодом, как следующий:

// Указываем параметры подключения.
cfg := mysql.Config{
    User:   username,
    Passwd: password,
    Net:    "tcp",
    Addr:   "127.0.0.1:3306",
    DBName: "jazzrecords",
}

// Получаем коннектор для конкретного драйвера.
connector, err := mysql.NewConnector(&cfg)
if err != nil {
    log.Fatal(err)
}

// Получаем дескриптор базы данных.
db = sql.OpenDB(connector)

Обработка ошибок

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

Подтверждение подключения

Когда вы открываете дескриптор базы данных, пакет sql может не сразу создать новое соединение с базой данных. Вместо этого он может создать соединение, когда это необходимо вашему коду. Если вы не собираетесь использовать базу данных сразу и хотите подтвердить, что соединение может быть установлено, вызовите Ping или PingContext.

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

db, err = sql.Open("mysql", connString)

// Подтверждаем успешное соединение.
if err := db.Ping(); err != nil {
    log.Fatal(err)
}

Хранение учетных данных базы данных

Избегайте хранения учетных данных базы данных в исходном Go коде, так как это может открыть доступ к содержимому вашей базы данных другим пользователям. Вместо этого найдите способ хранить их за пределами вашего кода, но доступными для него. Например, рассмотрим приложение-хранитель секретов, которое хранит учетные данные и предоставляет API, который ваш код может использовать для получения учетных данных для аутентификации в вашей СУБД.

Один из популярных подходов - хранить секреты в среде до запуска программы, возможно, загруженные из секретного менеджера, а затем ваша Go программа может прочитать их с помощью os.Getenv:

username := os.Getenv("DB_USER")
password := os.Getenv("DB_PASS")

Этот подход также позволяет вам самостоятельно устанавливать переменные среды для локального тестирования.

Освобождение ресурсов

Хотя вы не управляете и не закрываете соединения явно с помощью пакета database/sql, ваш код должен освобождать ресурсы, которые он получил, когда они больше не нужны. Они могут включать ресурсы, удерживаемые sql.Rows, представляющие данные, возвращенные из запроса, или sql.Stmt, представляющие подготовленный оператор.

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

Код в следующем примере откладывает Close, чтобы освободить ресурс, удерживаемый sql.Rows.

rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

// Цикл по возвращенным rows.


Читайте также:


Купить gopher

воскресенье, 29 августа 2021 г.

Работа с реляционными базами данных с Golang

Используя Go, вы можете включать в свои приложения самые разные базы данных и подходы к доступу к данным. В этом посте описывается, как использовать пакет стандартной библиотеки database/sql для доступа к реляционным базам данных.

Вводное руководство по доступу к данным с помощью Go в посте Доступ к реляционной базе данных.

Go также поддерживает другие технологии доступа к данным, включая библиотеки ORM для доступа более высокого уровня к реляционным базам данных, а также нереляционные хранилища данных NoSQL.

  • Библиотеки объектно-реляционного отображения (ORM). Хотя пакет database/sql включает функции для логики доступа к данным более низкого уровня, вы также можете использовать Go для доступа к хранилищам данных на более высоком уровне абстракции. Для получения дополнительных сведений о двух популярных библиотеках объектно-реляционного отображения (ORM) для Go GORM (справочник по пакету) и ent (справочник по пакету).
  • Хранилища данных NoSQL. Сообщество Go разработало драйверы для большинства хранилищ данных NoSQL, включая MongoDB и Couchbase.

Поддерживаемые системы управления базами данных

Go поддерживает все наиболее распространенные системы управления реляционными базами данных, включая MySQL, Oracle, Postgres, SQL Server, SQLite и другие.

Вы найдете полный список драйверов на странице SQLDrivers.

Функции для выполнения запросов или внесения изменений в базу данных

Пакет database/sql включает функции, специально разработанные для выполняемой вами операции с базой данных. Например, хотя вы можете использовать Query или QueryRow для выполнения запросов, QueryRow разработан для случая, когда вы ожидаете только одну строку, исключая накладные расходы на возврат sql.Rows, который включает только одну строку. Вы можете использовать функцию Exec для внесения изменений в базу данных с помощью операторов SQL, таких как INSERT, UPDATE или DELETE.

Транзакции

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

Отмена запроса

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

Для любой операции с базой данных вы можете использовать функцию пакета database/sql, которая принимает Context в качестве аргумента. Используя Context, вы можете указать таймаут или крайний срок для операции. Вы также можете использовать Context для передачи запроса отмены через ваше приложение функции, выполняющей инструкцию SQL, гарантируя, что ресурсы будут освобождены, если они больше не нужны.

Пул управляемых соединений

Когда вы используете дескриптор базы данных sql.DB, вы подключаетесь к встроенному пулу соединений, который создает и удаляет соединения в соответствии с потребностями вашего кода. Дескриптор через sql.DB - самый распространенный способ доступа к базе данных с помощью Go.

Пакет database/sql управляет пулом соединений за вас. Однако для более сложных задач вы можете установить свойства пула соединений.

Для тех операций, в которых требуется одно зарезервированное соединение, пакет database/sql предоставляет sql.Conn. Conn особенно полезен, когда транзакция с sql.Tx была бы плохим выбором.

Например, вашему коду может потребоваться:

  • Внести изменения в схему с помощью DDL, включая логику, содержащую собственную семантику транзакции. Смешивание функций транзакций пакета sql с операторами транзакции SQL - плохая практика.
  • Выполнить операции блокировки запросов, которые создают временные таблицы.

Читайте также:


Купить gopher

суббота, 28 августа 2021 г.

Доступ к реляционной базе данных c Golang

В этом руководстве представлены основы доступа к реляционной базе данных с помощью Go и пакета database/sql в его стандартной библиотеке.

Вы получите максимальную отдачу от этого руководства, если у вас будут базовые знания Go и его инструментов. Если это ваше первое знакомство с Go, смотрите: Начало работы с Go для быстрого ознакомления.

Пакет database/sql, который вы будете использовать, включает типы и функции для подключения к базам данных, выполнения транзакций, отмены выполняемой операции и т. д.

В этом руководстве вы создадите базу данных, а затем напишете код для доступа к базе данных. Ваш примерный проект будет репозиторием данных о винтажных джазовых записях.

В этом руководстве вы пройдете через следующие разделы:

  • Создадите папку для вашего кода.
  • Создадите базу данных.
  • Импортируете драйвер базы данных.
  • Получите дескриптор базы данных и подключитесь к ней.
  • Выполните запрос для нескольких строк.
  • Выполните запрос для одной строки.
  • Добавите данные.

Предпосылки

  • Установка системы управления реляционными базами данных (СУБД) MySQL.
  • Установка Go. Инструкции по установке смотрите в посте Установка Go.
  • Инструмент для редактирования вашего кода. Любой текстовый редактор, который у вас есть, будет работать нормально.
  • Командный терминал. Go хорошо работает с любым терминалом в Linux и Mac, а также с PowerShell или cmd в Windows.

Создайте папку для вашего кода

Для начала создайте папку для кода, который вы будете писать.

1. Откройте командную строку и перейдите в свой домашний каталог.

В Linux или Mac:

$ cd

В Windows:

C:\> cd %HOMEPATH%

В оставшейся части руководства мы будем показывать $ в качестве подсказки. Команды, которые мы используем, будут работать и в Windows.

2. В командной строке создайте каталог для вашего кода с именем data-access.

$ mkdir data-access
$ cd data-access

3. Создайте модуль, в котором вы сможете управлять зависимостями, которые вы добавите в ходе этого руководства.

Запустите команду go mod init, указав ей путь к модулю вашего нового кода.

$ go mod init example.com/data-access
go: creating new go.mod: module example.com/data-access

Эта команда создает файл go.mod, в котором будут перечислены добавленные вами зависимости для отслеживания.

Далее вы создадите базу данных.

Создание базы данных

На этом этапе вы создадите базу данных, с которой будете работать. Вы будете использовать интерфейс командной строки для самой СУБД для создания базы данных и таблицы, а также для добавления данных.

Вы создадите базу данных с данными о винтажных джазовых записях на виниле.

Код здесь использует MySQL CLI, но у большинства СУБД есть собственный CLI с аналогичными функциями.

1. Откройте новую командную строку.

2. В командной строке войдите в свою СУБД, как в следующем примере для MySQL.

$ mysql -u root -p
Enter password:

mysql> 

3. В командной строке mysql создайте базу данных.

mysql> create database recordings;

4. Измените только что созданную базу данных, чтобы можно было добавлять таблицы.

mysql> use recordings;
Database changed

5. В текстовом редакторе в папке data-access (которую мы ранее создали) создайте файл с именем create-tables.sql для хранения сценария SQL для добавления таблиц.

6. В файл вставьте следующий код SQL, затем сохраните файл.

DROP TABLE IF EXISTS album;
CREATE TABLE album (
  id         INT AUTO_INCREMENT NOT NULL,
  title      VARCHAR(128) NOT NULL,
  artist     VARCHAR(255) NOT NULL,
  price      DECIMAL(5,2) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO album 
  (title, artist, price) 
VALUES 
  ('Blue Train', 'John Coltrane', 56.99),
  ('Giant Steps', 'John Coltrane', 63.99),
  ('Jeru', 'Gerry Mulligan', 17.99),
  ('Sarah Vaughan', 'Sarah Vaughan', 34.98);

В этом коде SQL вы:

  • Удаляете (drop) таблицу под названием album. Выполнение этой команды вначале упрощает повторный запуск сценария позже, если вы хотите начать работу с таблицей заново.
  • Создаете таблицу album с четырьмя столбцами: id, title, artist, и price. Значение идентификатора каждой строки создается СУБД автоматически.
  • Добавляете три строки со значениями.

7. В командной строке mysql запустите только что созданный сценарий.

Вы будете использовать исходную команду в следующей форме:

mysql> source /путь/к/create-tables.sql

8. В командной строке СУБД используйте оператор SELECT, чтобы убедиться, что вы успешно создали таблицу с данными.

mysql> select * from album;
+----+---------------+----------------+-------+
| id | title         | artist         | price |
+----+---------------+----------------+-------+
|  1 | Blue Train    | John Coltrane  | 56.99 |
|  2 | Giant Steps   | John Coltrane  | 63.99 |
|  3 | Jeru          | Gerry Mulligan | 17.99 |
|  4 | Sarah Vaughan | Sarah Vaughan  | 34.98 |
+----+---------------+----------------+-------+
4 rows in set (0.00 sec)

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

Найдите и импортируйте драйвер базы данных

Теперь, когда у вас есть база данных с некоторыми данными, приступайте к работе над кодом Go.

Найдите и импортируйте драйвер базы данных, который будет переводить запросы, которые вы делаете с помощью функций в пакете database/sql, в запросы, которые понимает база данных.

1. В своем браузере посетите вики-страницу SQLDrivers, чтобы определить драйвер, который вы можете использовать.

Используйте список на странице, чтобы определить драйвер, который вы будете использовать. Для доступа к MySQL в этом руководстве вы будете использовать Go-MySQL-Driver.

2. Обратите внимание на имя пакета для драйвера - здесь github.com/go-sql-driver/mysql.

3. Используя текстовый редактор, создайте файл, в который будет записан код Go, и сохраните файл как main.go в каталоге data-access, который вы создали ранее.

4. В main.go вставьте следующий код, чтобы импортировать пакет драйвера.

package main

import "github.com/go-sql-driver/mysql"

В этом коде вы:

  • Добавляете свой код в main пакет, чтобы вы могли выполнять его независимо.
  • Импортируете драйвер MySQL с github.com/go-sql-driver/mysql.

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

Получите дескриптор базы данных и подключитесь к ней

Теперь напишите код Go, который дает вам доступ к базе данных с помощью дескриптора базы данных.

Вы будете использовать указатель на структуру sql.DB, которая предоставляет доступ к конкретной базе данных.

Напишите код

1. В main.go под только что добавленным кодом импорта вставьте следующий код Go, чтобы создать дескриптор базы данных.

var db *sql.DB

func main() {
    // Получаем свойства соединения.
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // Получаем дескриптор базы данных.
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")
}

В этом коде вы:

  • Объявляете переменную db типа *sql.DB. Это дескриптор вашей базы данных.
    Превращение db в глобальную переменную упрощает этот пример. В производственной среде вы не должны использовать глобальную переменную, например, передав ее функциям, которым она нужна, или заключив ее в структуру.
  • Используете Config драйвера MySQL и FormatDSN типа, чтобы собрать свойства соединения и отформатировать их в DSN для строки соединения.
    Структура Config упрощает чтение кода, вместо простой строки подключения.
  • Вызываете sql.Open, чтобы инициализировать переменную db, передав возвращаемое значение FormatDSN.
  • Проверяете наличие ошибки в sql.Open. Это может произойти, если, например, ваши особенности подключения к базе данных не были правильно сформированы.
    Чтобы упростить код, вы вызываете log.Fatal, чтобы завершить выполнение и вывести ошибку на консоль. В производственном коде вы захотите более аккуратно обрабатывать ошибки.
  • Вызываете DB.Ping, чтобы убедиться, что подключение к базе данных работает. Во время выполнения sql.Open может подключиться не сразу, в зависимости от драйвера. Здесь вы используете команду Ping, чтобы подтвердить, что пакет database/sql может подключиться, когда это необходимо.
  • Проверяете наличие ошибки от Ping, на случай, если соединение не удалось.
  • Распечатываете сообщение, если Ping успешно установит соединение.

2. В верхней части файла main.go, сразу под объявлением пакета, импортируйте пакеты, которые вам понадобятся для поддержки только что написанного кода.

Теперь верх файла должен выглядеть так:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/go-sql-driver/mysql"
)

3. Сохраните main.go.

Запустите код

1. Начните отслеживать модуль драйвера MySQL как зависимость.

Используйте go get, чтобы добавить модуль github.com/go-sql-driver/mysql в качестве зависимости для вашего собственного модуля. Используйте аргумент с точкой для обозначения "получить зависимости для кода в текущем каталоге".

$ go get .
go get: added github.com/go-sql-driver/mysql v1.6.0

Go загрузил эту зависимость, потому что вы добавили ее в объявление импорта на предыдущем шаге.

2. В командной строке установите переменные среды DBUSER и DBPASS для использования программой Go.

В Linux или Mac:

$ export DBUSER=username
$ export DBPASS=password

В Windows:

C:\Users\you\data-access> set DBUSER=username
C:\Users\you\data-access> set DBPASS=password

3. В командной строке в каталоге, содержащем main.go, запустите код, набрав go run с аргументом точка, что означает "запустить пакет в текущем каталоге".

$ go run .
Connected!

Вы можете подключиться! Далее вы запросите некоторые данные.

Запрос для нескольких строк

В этом разделе вы будете использовать Go для выполнения SQL-запроса, предназначенного для возврата нескольких строк.

Для операторов SQL, которые могут возвращать несколько строк, вы используете метод Query из пакета database/sql, а затем перебираете строки, которые он возвращает.

Напишите код

1. В main.go сразу над func main вставьте следующее определение структуры Album. Вы будете использовать ее для хранения данных строк, возвращенных из запроса.

type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}

2. Под func main вставьте следующую функцию albumByArtist для запроса базы данных.

// albumsByArtist запрашивает альбомы с указанным именем исполнителя.
func albumsByArtist(name string) ([]Album, error) {
    // Срез альбомов для хранения данных из возвращенных строк.
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // Цикл по строкам, используя Scan для назначения данных столбца полям структуры.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}

В этом коде вы:

  • Объявляете срез albums определенного вами типа Album. Он будет содержать данные из возвращенных строк. Имена и типы полей структуры соответствуют именам и типам столбцов базы данных.
  • Используете DB.Query для выполнения оператора SELECT для запроса альбомов с указанным именем исполнителя.
    Первый параметр запроса - это оператор SQL. После параметра вы можете передать ноль или более параметров любого типа. Они предоставляют вам место для указания значений параметров в вашем операторе SQL. Отделяя инструкцию SQL от значений параметров (а не объединяя их, скажем, с fmt.Sprintf), вы разрешаете пакету database/sql отправлять значения отдельно от текста SQL, устраняя любой риск внедрения SQL-кода.
  • Откладываете закрытие rows, чтобы все ресурсы, которые она хранит, были освобождены при выходе из функции.
  • Проходите в цикле по возвращенным строкам, используя Rows.Scan, чтобы назначить значения столбцов каждой строки полям структуры Album.
    Scan принимает список указателей на значения Go, куда будут записаны значения столбцов. Здесь вы передаете указатели на поля в переменной alb, созданные с помощью оператора &. Scan записывает через указатели для обновления полей структуры.
  • Внутри цикла проверяете, нет ли ошибки при сканировании значений столбцов в поля структуры.
  • Внутри цикла добавляете новый альбом к срезу альбомов.
  • После цикла проверяете, нет ли ошибки в общем запросе, используя rows.Err. Обратите внимание, что если сам запрос завершается ошибкой, проверка ошибки здесь - единственный способ узнать, что результаты являются неполными.

3. Обновите свою main функцию, чтобы она вызывала albumByArtist.

В конец func main добавьте следующий код.

albums, err := albumsByArtist("John Coltrane")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)

В новом коде вы:

  • Вызываете добавленную функцию albumByArtist, присвоив ее возвращаемое значение новой переменной albums.
  • Распечатываете результат.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]

Далее вы запросите одну строку.

Запрос для одной строки

В этом разделе вы будете использовать Go для запроса одной строки из базы данных.

Для операторов SQL, которые, как вы знаете, вернут не более одной строки, вы можете использовать QueryRow, что проще, чем использование цикла Query.

Напишите код

1. Под albumsByArtist вставьте следующую функцию albumByID.

// albumByID запрашивает альбом с указанным идентификатором.
func albumByID(id int64) (Album, error) {
    // Альбом для хранения данных из возвращенной строки.
    var alb Album

    row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
        if err == sql.ErrNoRows {
            return alb, fmt.Errorf("albumsById %d: no such album", id)
        }
        return alb, fmt.Errorf("albumsById %d: %v", id, err)
    }
    return alb, nil
}

В этом коде вы:

  • Используете DB.QueryRow для выполнения инструкции SELECT для запроса альбома с указанным идентификатором.
    Он возвращает sql.Row. Чтобы упростить вызывающий код, QueryRow не возвращает ошибку. Вместо этого он организует возврат любой ошибки запроса (например, sql.ErrNoRows) из Rows.Scan позже.
  • Используете Row.Scan, чтобы скопировать значения столбцов в поля структуры.
  • Проверяете наличие ошибок при сканировании.
    Специальная ошибка sql.ErrNoRows указывает, что запрос не вернул строк. Обычно эту ошибку следует заменить более конкретным текстом, например "нет такого альбома".

2. Обновите main для вызова albumByID.

В конец func main добавьте следующий код.

// ID 2 зафиксирован здесь для проверки запроса.
alb, err := albumByID(2)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)

В этом коде вы:

  • Вызываете добавленную функцию albumByID.
  • Распечатываете возвращенный идентификатор альбома.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}

Далее вы добавите альбом в базу данных.

Добавить данные

В этом разделе вы воспользуетесь Go для выполнения инструкции SQL INSERT для добавления новой строки в базу данных.

Вы видели, как использовать Query и QueryRow с операторами SQL, которые возвращают данные. Чтобы выполнять операторы SQL, которые не возвращают данные, используйте Exec.

Напишите код

1. Под albumByID вставьте следующую функцию addAlbum, чтобы вставить новый альбом в базу данных, затем сохраните файл main.go.

// addAlbum добавляет указанный альбом в базу данных,
// возвращает ID альбома новой записи
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}

В этом коде вы:

  • Используете DB.Exec для выполнения инструкции INSERT.
    Как и Query, Exec принимает оператор SQL, за которым следуют значения параметров для оператора SQL.
  • Проверяете, нет ли ошибки при попытке INSERT.
  • Получаете идентификатор вставленной строки базы данных с помощью result.LastInsertId.
  • Проверяете, нет ли ошибки при попытке получить идентификатор.

2. Обновите main для вызова новой функции addAlbum.

В конец func main добавьте следующий код.

albID, err := addAlbum(Album{
    Title:  "The Modern Sound of Betty Carter",
    Artist: "Betty Carter",
    Price:  49.99,
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)

В новом коде вы теперь:

Вызываете addAlbum с новым альбомом, назначив идентификатор альбома, который вы добавляете, переменной albID.

Запустите код

Из командной строки в каталоге, содержащем main.go, запустите код.

$ go run .
Connected!
Albums found: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
Album found: {2 Giant Steps John Coltrane 63.99}
ID of added album: 5

Готовый код

В этом разделе содержится код приложения, которое вы создаете с помощью этого руководства.

package main

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    "github.com/go-sql-driver/mysql"
)

var db *sql.DB

type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}

func main() {
    // Получаем свойства соединения.
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // Получаем дескриптор базы данных.
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")

    albums, err := albumsByArtist("John Coltrane")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Albums found: %v\n", albums)

    // ID 2 зафиксирован здесь для проверки запроса.
    alb, err := albumByID(2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Album found: %v\n", alb)

    albID, err := addAlbum(Album{
        Title:  "The Modern Sound of Betty Carter",
        Artist: "Betty Carter",
        Price:  49.99,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID of added album: %v\n", albID)
}

// albumsByArtist запрашивает альбомы с указанным именем исполнителя.
func albumsByArtist(name string) ([]Album, error) {
    // Срез альбомов для хранения данных из возвращенных строк.
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // Цикл по строкам, используя Scan для назначения данных столбца полям структуры.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}

// albumByID запрашивает альбом с указанным идентификатором.
func albumByID(id int64) (Album, error) {
    // Альбом для хранения данных из возвращенной строки.
    var alb Album

    row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
    if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
        if err == sql.ErrNoRows {
            return alb, fmt.Errorf("albumsById %d: no such album", id)
        }
        return alb, fmt.Errorf("albumsById %d: %v", id, err)
    }
    return alb, nil
}

// addAlbum добавляет указанный альбом в базу данных,
// возвращает ID альбома новой записи
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}


Читайте также:


Купить gopher

воскресенье, 31 мая 2020 г.

Работа с базами данных в Golang. Пакет database/sql в Golang

Пакет database/sql предоставляет общий интерфейс для баз данных SQL (или SQL-подобных).

Драйвер базы данных

Пакет database/sql должен использоваться вместе с драйвером базы данных.

В приведенном ниже описании предполагается, что драйвер был импортирован.

Подключение к базе данных

Open используется для создания дескриптора базы данных:

db, err := sql.Open(driver, dataSourceName)

Где driver указывает драйвер базы данных, а dataSourceName указывает информацию о подключении к базе данных, такую ​​как имя базы данных и учетные данные для аутентификации.

Обратите внимание, что Open не открывает соединение с базой данных напрямую: это откладывается до выполнения запроса. Чтобы убедиться, что соединение может быть установлено перед выполнением запроса, используйте метод PingContext:

if err := db.PingContext(ctx); err != nil {
    log.Fatal(err)
}

После использования база данных закрывается с помощью метода Close.

Выполнение запросов

ExecContext используется для запросов, где строки ответа (rows) не возвращаются:

result, err := db.ExecContext(ctx,
    "INSERT INTO users (name, age) VALUES ($1, $2)",
    "gopher",
    27,
)

Где result содержит идентификатор последней вставки и количество затронутых строк. Доступность этих значений зависит от драйвера базы данных.

QueryContext используется для поиска:

rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age = $1", age)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
    var name string
    if err := rows.Scan(&name); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s is %d\n", name, age)
}
if err := rows.Err(); err != nil {
    log.Fatal(err)
}

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

var age int64
err := db.QueryRowContext(ctx, "SELECT age FROM users WHERE name = $1", name).Scan(&age)

Подготовленные утверждения (statements) могут быть созданы с PrepareContext:

age := 27
stmt, err := db.PrepareContext(ctx, "SELECT name FROM users WHERE age = $1")
if err != nil {
    log.Fatal(err)
}
rows, err := stmt.Query(age)
// обработка строк ответа

ExecContext, QueryContext и QueryRowContext могут быть вызваны для утверждений (statements). После использования утверждение должно быть закрыто с помощью Close.

Транзакции

Транзакции начинаются с BeginTx:

tx, err := db.BeginTx(ctx, nil)
if err != nil {
    log.Fatal(err)
}

Уже описанные методы ExecContext, QueryContext, QueryRowContext и PrepareContext могут использоваться в транзакции.

Транзакция должна завершиться вызовом Commit или Rollback.

Работа с NULL

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

Например, если столбец name в таблице names обнуляется:

var name sql.NullString
err := db.QueryRowContext(ctx, "SELECT name FROM names WHERE id = $1", id).Scan(&name)
...
if name.Valid {
    // используем name.String
} else {
    // значение равно NULL
}

В database/sql реализованы только NullBool, NullFloat64, NullInt64, NullInt32, NullString и NullTime. Реализации специфических для базы данных нулевых типов оставлены для драйвера базы данных. Пользовательские типы, поддерживающие NULL, могут быть созданы путем реализации интерфейсов database/sql/driver.Valuer и database/sql.Scanner.


Читайте также: