воскресенье, 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

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

Релиз Go 1.17

Введение в Go 1.17

Релиз Go, версия 1.17, выходит через шесть месяцев после Go 1.16. Большинство его изменений связано с реализацией набора инструментов, среды выполнения и библиотек. Как всегда, релиз поддерживает обещание Go 1 о совместимости. Ожидается, что почти все программы Go продолжат компилироваться и работать как раньше.

Изменения языка

Go 1.17 включает три небольших улучшения языка.

1. Преобразования из среза в указатель на массив: выражение s типа []T теперь может быть преобразовано в тип указателя массива *[N]T. Если a является результатом такого преобразования, то соответствующие индексы, находящиеся в диапазоне, относятся к одним и тем же базовым элементам: &a[i] == &s[i] для 0 <= i < N. Преобразование вызывает панику, если len(s) меньше N.

2. unsafe.Add: unsafe.Add(ptr, len) добавляет len к ptr и возвращает обновленный указатель unsafe.Pointer(uintptr(ptr) + uintptr(len)).

3. unsafe.Slice: для выражения ptr типа *T, unsafe.Slice(ptr, len) возвращает срез типа []T, базовый массив которого начинается с ptr, а длина и емкость равны len.

Улучшения в пакет unsafe были добавлены, чтобы упростить написание кода, который соответствует правилам безопасности unsafe.Pointer, но правила остаются неизменными. В частности, существующие программы, которые правильно используют unsafe.Pointer, остаются действительными, а новые программы должны по-прежнему следовать правилам при использовании unsafe.Add или unsafe.Slice.

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

Порты

Darwin

Как было объявлено в примечаниях к релизу Go 1.16, для Go 1.17 требуется macOS 10.13 High Sierra или новее; поддержка предыдущих версий прекращена.

Windows

Go 1.17 добавляет поддержку 64-битной архитектуры ARM в Windows (порт windows/arm64). Этот порт поддерживает cgo.

OpenBSD

64-битная архитектура MIPS в OpenBSD (порт openbsd/mips64) теперь поддерживает cgo.

В Go 1.16 на 64-битных x86 и 64-битных архитектурах ARM на OpenBSD (порты openbsd/amd64 и openbsd/arm64) системные вызовы выполняются через libc, а не напрямую с использованием машинных инструкций. В Go 1.17 это также делается в 32-битных x86 и 32-битных архитектурах ARM в OpenBSD (порты openbsd/386 и openbsd/arm). Это обеспечивает совместимость с OpenBSD 6.9 и новее, которые требуют, чтобы системные вызовы выполнялись через libc для нестатических двоичных файлов Go.

ARM64

Программы Go теперь поддерживают указатели фреймов стека в 64-битной архитектуре ARM во всех операционных системах. Ранее он поддерживал указатели фреймов стека только в Linux, macOS и iOS.

loong64 GOARCH значение зарезервировано

Основной компилятор Go еще не поддерживает архитектуру LoongArch, но зарезервировано для GOARCH значение loong64. Это означает, что файлы Go с именем *_loong64.go теперь будут игнорироваться инструментами Go, кроме случаев, когда используется это значение GOARCH.

Инструменты


Команда go


Обрезанные графы модулей в модулях go 1.17

Если модуль указывает go 1.17 или выше, граф модулей включает только непосредственные зависимости других модулей go 1.17, но не их полные транзитивные зависимости.

Чтобы команда go правильно разрешала транзитивный импорт с использованием обрезанного графа модуля, файл go.mod для каждого модуля должен включать более подробную информацию о транзитивных зависимостях, относящихся к этому модулю. Если модуль указывает go 1.17 или выше в своем файле go.mod, его файл go.mod теперь содержит явную директиву require для каждого модуля, который предоставляет транзитивно-импортированный пакет. (В предыдущих версиях файл go.mod обычно включал явные требования только для напрямую импортированных пакетов.)

Поскольку расширенный файл go.mod, необходимый для сокращения графа модуля, включает все зависимости, необходимые для загрузки импорта любого пакета в основной модуль, если в основном модуле указано go 1.17 или выше, инструмент go больше не читает (или даже не загружает) go.mod для зависимостей, если они не нужны для выполнения запрошенной команды.

Поскольку количество явных требований может быть значительно больше в расширенном файле Go 1.17 go.mod, недавно добавленные требования по косвенным зависимостям в модуле go 1.17 поддерживаются в блоке require отдельном от блока, содержащего прямые зависимости.

Чтобы упростить обновление до сокращенных графов модулей Go 1.17, подкоманда go mod tidy теперь поддерживает флаг -go для установки или изменения версии go в файле go.mod. Чтобы преобразовать файл go.mod для существующего модуля в Go 1.17 без изменения выбранных версий его зависимостей, запустите:

go mod tidy -go=1.17

По умолчанию go mod tidy проверяет, что выбранные версии зависимостей, относящиеся к основному модулю, совпадают с версиями, которые использовались бы в предыдущем выпуске Go (Go 1.16 для модуля, который определяет go 1.17), и сохраняет записи go.sum необходимо в этом релизе даже для зависимостей, которые обычно не нужны другим командам.

Флаг -compat позволяет переопределить эту версию для поддержки более старых (или только более новых) версий, вплоть до версии, указанной директивой go в файле go.mod. Чтобы привести в порядок модуль go 1.17 только для Go 1.17, без сохранения контрольных сумм (или проверки согласованности с) Go 1.16:

go mod tidy -compat=1.17

Обратите внимание, что даже если основной модуль приведен в порядок с -compat=1.17, пользователи, которым требуется модуль из модуля go 1.16 или более ранней версии, все равно смогут использовать его, при условии, что пакеты используют только совместимые языковые и библиотечные функции.

Подкоманда go mod graph также поддерживает флаг -go, который заставляет его сообщать граф в том виде, в каком он виден указанной версией Go, показывая зависимости, которые в противном случае могут быть удалены.

Комментарии к устаревшим модулям

Авторы модуля могут объявить модуль устаревшим, добавив комментарий // Deprecated: в go.mod, а затем пометив новую версию. go get теперь выводит предупреждение, если модуль, необходимый для сборки пакетов, указанных в командной строке, устарел. go list -m -u печатает устаревшие для всех зависимостей (используйте -f или -json, чтобы показать полное сообщение). Команда go считает разные основные версии отдельными модулями, поэтому этот механизм можно использовать, например, для предоставления пользователям инструкций по миграции для новой основной версии.

go get

Флаг go get -insecure устарел и был удален. Чтобы разрешить использование небезопасных схем при получении зависимостей, используйте переменную среды GOINSECURE. Флаг -insecure также обходит проверку суммы модуля, используйте GOPRIVATE или GONOSUMDB, если вам нужна эта функция.

go get выводит предупреждение об устаревании при установке команд вне основного модуля (без флага -d). go install cmd@version следует использовать вместо этого для установки команды определенной версии, используя суффикс типа @latest или @v1.2.3. В Go 1.18 всегда будет включен флаг -d, а go get будет использоваться только для изменения зависимостей в go.mod.

В файлах go.mod отсутствуют директивы go

Если файл go.mod основного модуля не содержит директивы go и команда go не может обновить файл go.mod, команда go теперь предполагает go 1.11 вместо текущего релиза. (go mod init добавляет директивы go автоматически, начиная с Go 1.12.)

Если в зависимости модуля отсутствует явный файл go.mod или его файл go.mod не содержит директивы go, команда go теперь предполагает go 1.16 для этой зависимости вместо текущего релиза. (В зависимостях, разработанных в режиме GOPATH, может отсутствовать файл go.mod, а в vendor/modules.txt до настоящего времени никогда не записывались версии go, указанные в файлах зависимостей go.mod.)

Содержимое vendor

Если в основном модуле указано go 1.17 или выше, go mod vendor теперь аннотирует vendor/modules.txt версией go, указанную каждым поставляемым модулем, в его собственном файле go.mod. Аннотированная версия используется при сборке пакетов модуля из исходного кода поставщика.

Если основной модуль указывает go 1.17 или выше, go mod vendor теперь не включает файлы go.mod и go.sum для зависимостей поставщика, которые в противном случае могут помешать команде go определить правильный корень модуля при вызове в дереве поставщиков.

Запросы пароля

Команда go по умолчанию теперь подавляет запросы пароля SSH и запросы Git Credential Manager при извлечении репозиториев Git с помощью SSH, как это уже делалось ранее для других запросов пароля Git. Пользователи, аутентифицирующиеся в частных репозиториях Git с защищенным паролем SSH, могут настроить ssh-agent, чтобы разрешить команде go использовать защищенные паролем ключи SSH.

go mod download

Когда go mod download вызывается без аргументов, он больше не будет сохранять суммы для загруженного содержимого модуля в go.sum. Он все еще может вносить изменения в go.mod и go.sum, необходимые для загрузки списка сборки. Это то же самое, что и в Go 1.15. Чтобы сохранить суммы для всех модулей, используйте go mod download all.

//go:build строки

Команда go теперь понимает строки //go:build и предпочитает их строкам // +build. В новом синтаксисе используются логические выражения, как и в Go, и он должен быть менее подвержен ошибкам. Начиная с этого релиза, новый синтаксис полностью поддерживается, и все файлы Go должны быть обновлены, чтобы обе формы имели одинаковое значение. Чтобы облегчить миграцию, gofmt теперь автоматически синхронизирует две формы.

go run

go run теперь принимает аргументы с суффиксами версии (например, go run example.com/cmd@v1.0.0). Это приводит к тому, что go run создает и запускает пакеты в режиме с поддержкой модулей, игнорируя файл go.mod в текущем каталоге или любом родительском каталоге, если он есть. Это полезно для запуска исполняемых файлов без их установки или без изменения зависимостей текущего модуля.

gofmt

gofmt (и go fmt) теперь синхронизирует строки //go:build со строками // +build. Если файл содержит только строки // +build, они будут перемещены в соответствующее место в файле, и будут добавлены соответствующие строки //go:build. В противном случае строки // +build будут перезаписаны на основе любых существующих строк //go:build.

vet

Новое предупреждение для несоответствующих строк //go:build и // +build

Инструмент vet теперь проверяет, что строки //go:build и // +build находятся в правильной части файла и синхронизированы друг с другом. Если это не так, для их исправления можно использовать gofmt.

Новое предупреждение о вызывающем signal.Notify на небуферизованных каналах.

Инструмент vet теперь предупреждает о вызовах на signal.Notify входящими сигналами, отправляемыми в небуферизованный канал. Использование небуферизованного канала рискует пропустить сигналы, отправленные по ним так как signal.Notify не блокируется при отправке в канал. Например:

c := make(chan os.Signal)
// сигналы отправляются на c до чтения канала.
// Этот сигнал может быть отброшен, поскольку c небуферизован.
signal.Notify(c, os.Interrupt)

Пользователи signal.Notify должны использовать каналы с достаточным буферным пространством, чтобы поддерживать ожидаемую скорость сигнала.

Новые предупреждения для методов Is, As и Unwrap

Инструмент vet теперь предупреждает о методах с именами As, Is или Unwrap для типов, реализующих интерфейс error, которые имеют сигнатуру, отличную от той, которую ожидает пакет errors. Функции errors.{As,Is,Unwrap} ожидают, что такие методы будут реализовывать либо Is(error) bool, As(interface{}) bool, либо Unwrap() error соответственно. Функции errors.{As,Is,Unwrap} будут игнорировать методы с такими же именами, но с другой сигнатурой. Например:

type MyError struct { hint string }
func (m MyError) Error() string { ... } // MyError реализует error.
func (MyError) Is(target interface{}) bool { ... } // target это interface{} вместо error.
func Foo() bool {
	x, y := MyError{"A"}, MyError{"B"}
	return errors.Is(x, y) 
	// возвращает false так как x != y и MyError не имеет `Is(error) bool` функции.
}

cover

Инструмент cover теперь использует оптимизированный синтаксический анализатор из golang.org/x/tools/cover, который может быть заметно быстрее при синтаксическом анализе больших профилей покрытия.

Компилятор

Go 1.17 реализует новый способ передачи аргументов функции и результатов с использованием регистров вместо стека. Тесты для репрезентативного набора пакетов и программ Go показывают повышение производительности примерно на 5%, а типичное уменьшение размера двоичного файла - примерно на 2%. В настоящее время это включено для Linux, macOS и Windows на 64-битной архитектуре x86 (порты linux/amd64, darwin/amd64 и windows/amd64).

Это изменение не влияет на функциональность любого безопасного кода Go и не влияет на большую часть кода сборки. Это может повлиять на код, который нарушает правила unsafe.Pointer при доступе к аргументам функции или который зависит от недокументированного поведения, связанного со сравнением указателей кода функции. Для обеспечения совместимости с существующими функциями сборки компилятор создает функции адаптера, которые преобразуют новое соглашение о вызовах на основе регистров и предыдущее соглашение о вызовах на основе стека. Эти адаптеры, как правило, невидимы для пользователей, за исключением того, что они принимают адрес функции Go в коде сборки или принимают адрес функции сборки в коде Go с помощью метода reflect.ValueOf(fn).Pointer() или unsafe.Pointer будут теперь возвращать адрес адаптера. Код, зависящий от значения этих указателей кода, может больше не вести себя должным образом. Адаптеры также могут вызвать очень небольшие накладные расходы в двух случаях: косвенный вызов функции сборки из Go через значение func и вызов функций Go из сборки.

Улучшен формат трассировки стека из среды выполнения (печатается при возникновении неперехваченной паники или при вызове runtime.Stack). Ранее аргументы функции выводились как шестнадцатеричные слова в зависимости от структуры памяти. Теперь каждый аргумент в исходном коде печатается отдельно, через запятую. Аргументы агрегатного типа (структура, массив, строка, срез, интерфейс и комплексные) разделяются фигурными скобками. Предостережение: значение аргумента, который живет только в регистре и не сохраняется в памяти, может быть неточным. Значения, возвращаемые функцией (которые обычно были неточными), больше не печатаются.

Функции, содержащие замыкания, теперь могут быть встроены. Одним из следствий этого изменения является то, что функция с замыканием может создавать отдельный указатель кода замыкания для каждого места, в которое встроена функция. Значения функций Go нельзя напрямую сравнивать, но это изменение может выявить ошибки в коде, который использует Reflection или unsafe.Pointer для обхода этого языкового ограничения и сравнения функций по указателю кода.

Компоновщик (Linker)

Когда компоновщик использует режим внешней компоновки, который используется по умолчанию при компоновке программы, использующей cgo, и компоновщик вызывается с параметром -I, этот параметр теперь будет передан внешнему компоновщику как -Wl, --dynamic-linker опция.

Основная библиотека


Cgo

Пакет runtime/cgo теперь предоставляет новую возможность, которая позволяет преобразовывать любые значения Go в безопасное представление, которое можно использовать для безопасной передачи значений между C и Go (runtime/cgo.Handle).

Разбор URL-запроса

Пакеты net/url и net/http, используемые для приема ";" (точка с запятой) в качестве разделителя настроек в запросах URL в дополнение к "&" (амперсанд). Теперь настройки с точками с запятой, не закодированными в процентах, отклоняются, и серверы net/http будут регистрировать предупреждение в Server.ErrorLog при обнаружении такого предупреждения в URL-адресе запроса.

Например, до Go 1.17 метод запроса URL-адреса example?a=1;b=2&c=3 возвращал бы map[a:[1] b:[2] c:[3]], а теперь возвращает map[c:[3]].

При обнаружении такой строки запроса URL.Query и Request.FormValue игнорируют любые параметры, содержащие точку с запятой, ParseQuery возвращает оставшиеся параметры и ошибку, а Request.ParseForm и Request.ParseMultipartForm возвращают ошибку, но по-прежнему устанавливают поля запроса на основе остальные настройки.

Пользователи net/http могут восстановить исходное поведение с помощью новой оболочки обработчика AllowQuerySemicolons. Это также подавит предупреждение ErrorLog. Обратите внимание, что использование точек с запятой в качестве разделителей запросов может привести к проблемам с безопасностью, если разные системы по-разному интерпретируют ключи кеша.

TLS строгий ALPN

Когда установлен Config.NextProtos, серверы теперь обеспечивают перекрытие между настроенными протоколами и протоколами ALPN, объявленными клиентом, если таковые имеются. Если нет взаимно поддерживаемого протокола, соединение закрывается с предупреждением no_application_protocol, как того требует RFC 7301. Это помогает смягчить межпротокольную атаку ALPACA.

В качестве исключения, когда значение "h2" включено в файл Config.NextProtos сервера, клиентам HTTP/1.1 будет разрешено подключаться, как если бы они не поддерживали ALPN.

Незначительные изменения в библиотеке

Как всегда, в библиотеку внесены различные незначительные изменения и обновления, сделанные с учетом обещания совместимости с Go 1.

archive/zip

Новые методы File.OpenRaw, Writer.CreateRaw, Writer.Copy обеспечивают поддержку в случаях, когда производительность является основной проблемой.

bufio

Метод Writer.WriteRune теперь записывает заменяющий символ U+FFFD для отрицательных значений рун, как и для других недопустимых рун.

bytes

Метод Buffer.WriteRune теперь записывает заменяющий символ U+FFFD для отрицательных значений рун, как и для других недопустимых рун.

compress/lzw

Функция NewReader гарантированно возвращает значение нового типа Reader, и аналогично NewWriter гарантированно возвращает значение нового типа Writer. Оба этих новых типа реализуют метод Reset (Reader.Reset, Writer.Reset), который позволяет повторно использовать Reader или Writer.

crypto/ed25519

Пакет crypto/ed25519 был переписан, и теперь все операции на amd64 и arm64 выполняются примерно в два раза быстрее. В остальном наблюдаемое поведение не изменилось.

crypto/elliptic

Методы CurveParams теперь автоматически вызывают более быстрые и безопасные специализированные реализации для известных кривых (P-224, P-256 и P-521), когда они доступны. Обратите внимание, что это наилучший подход, и приложениям следует избегать использования общих методов CurveParams, а не методов CurveParams с постоянным временем, а вместо этого использовать специальные реализации Curve, такие как P256.

Реализация кривой P521 была переписана с использованием кода, созданного проектом fiat-crypto, который основан на официально проверенной модели арифметических операций. Теперь он работает с постоянным временем и в три раза быстрее на amd64 и arm64. В остальном наблюдаемое поведение не изменилось.

crypto/rand

Пакет crypto/rand теперь использует системный вызов getentropy в macOS и системный вызов getrandom в Solaris, Illumos и DragonFlyBSD.

crypto/tls

Новый метод Conn.HandshakeContext позволяет пользователю управлять отменой текущего подтверждения TLS. Предоставленный контекст доступен из различных обратных вызовов через новые методы ClientHelloInfo.Context и CertificateRequestInfo.Context. Отмена контекста после завершения рукопожатия не имеет никакого эффекта.

Порядок набора шифров теперь полностью обрабатывается пакетом crypto/tls. В настоящее время комплекты шифров сортируются в зависимости от их безопасности, производительности и аппаратной поддержки, принимая во внимание как локальное, так и одноранговое оборудование. Порядок поля Config.CipherSuites теперь игнорируется, а также поля Config.PreferServerCipherSuites. Обратите внимание, что Config.CipherSuites по-прежнему позволяет приложениям выбирать, какие комплекты шифров TLS 1.0–1.2 включить.

Наборы шифров 3DES были перемещены в InsecureCipherSuites из-за фундаментальной слабости, связанной с размером блока. Они по-прежнему включены по умолчанию, но только в крайнем случае, благодаря изменению порядка набора шифров, описанному выше.

Начиная со следующего релиза, Go 1.18, Config.MinVersion для клиентов crypto/tls по умолчанию будет использовать TLS 1.2, отключая TLS 1.0 и TLS 1.1 по умолчанию. Приложения смогут отменять изменение, явно задав Config.MinVersion. Это не повлияет на серверы crypto/tls.

crypto/x509

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

Временный флаг GODEBUG=x509ignoreCN=0 был удален.

ParseCertificate был переписан и теперь потребляет на ~70% меньше ресурсов. В остальном наблюдаемое поведение не изменилось, за исключением сообщений об ошибках.

В системах BSD теперь выполняется поиск доверенных корней в /etc/ssl/certs. Это добавляет поддержку нового системного хранилища доверенных сертификатов во FreeBSD 12.2+.

Начиная со следующего выпуска, Go 1.18, crypto/x509 будет отклонять сертификаты, подписанные с помощью хеш-функции SHA-1. Это не относится к самозаверяющим корневым сертификатам. Практические атаки на SHA-1 были продемонстрированы в 2017 году, а публично доверенные центры сертификации не выпускали сертификаты SHA-1 с 2015 года.

database/sql

Метод DB.Close теперь закрывает поле коннектора, если тип в этом поле реализует интерфейс io.Closer.

Новые структуры NullInt16 и NullByte представляют значения int16 и byte, которые могут быть нулевыми. Их можно использовать в качестве адресатов метода Scan, аналогично NullString.

debug/elf

Добавлена константа SHT_MIPS_ABIFLAGS.

encoding/binary

binary.Uvarint перестанет читать после 10 байт, чтобы избежать ненужных вычислений. Если требуется более 10 байтов, возвращается -11 байтов.

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

encoding/csv

Новый метод Reader.FieldPos возвращает строку и столбец, соответствующие началу данного поля в записи, последней возвращенной Read.

encoding/xml

Когда комментарий появляется в Directive, он теперь заменяется одним пробелом, а не полностью опускается.

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

flag

Объявления флагов теперь вызывают панику, если указано недопустимое имя.

go/build

Новое поле Context.ToolTags содержит теги сборки, соответствующие текущей конфигурации цепочки инструментов Go.

go/format

Функции Source и Node теперь синхронизируют строки //go:build со строками // +build. Если файл содержит только строки // +build, они будут перемещены в соответствующее место в файле, и будут добавлены соответствующие строки //go:build. В противном случае строки // +build будут перезаписаны на основе любых существующих строк //go:build.

go/parser

Новое значение режима SkipObjectResolution указывает синтаксическому анализатору не разрешать идентификаторы для их объявления. Это может улучшить скорость синтаксического анализа.

image

Конкретные типы изображений (RGBA, Gray16 и т. д.) Теперь реализуют новый интерфейс RGBA64Image. Конкретные типы, которые ранее реализовывали draw.Image, теперь также реализуют draw.RGBA64Image, новый интерфейс в пакете image/draw.

io/fs

Новая функция FileInfoToDirEntry преобразует FileInfo в DirEntry.

math

Пакет math теперь определяет еще три константы: MaxUint, MaxInt и MinInt. Для 32-битных систем их значения равны 2^32 - 1, 2^31 - 1 и -2^31 соответственно. Для 64-битных систем их значения составляют 2^64 - 1, 2^63 - 1 и -2^63 соответственно.

mime

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

mime/multipart

Part.FileName теперь применяет filepath.Base к возвращаемому значению. Это снижает потенциальные уязвимости обхода пути в приложениях, которые принимают составные сообщения, таких как серверы net/http, которые вызывают Request.FormFile.

net

Новый метод IP.IsPrivate сообщает, является ли адрес частным IPv4-адресом согласно RFC 1918 или локальным IPv6-адресом согласно RFC 4193.

Теперь преобразователь Go DNS отправляет только один DNS-запрос при разрешении адреса для сети только IPv4 или IPv6, а не запрашивает оба семейства адресов.

Тип ошибки ErrClosed и ошибки ParseError теперь реализуют интерфейс net.Error.

Функции ParseIP и ParseCIDR теперь отклоняют адреса IPv4, которые содержат десятичные компоненты с ведущими нулями. Эти компоненты всегда интерпретировались как десятичные, но некоторые операционные системы обрабатывают их как восьмеричные. Это несоответствие могло бы гипотетически привести к проблемам безопасности, если бы приложение Go использовалось для проверки IP-адресов, которые затем использовались в их исходной форме с приложениями, отличными от Go, которые интерпретировали компоненты как восьмеричные. Как правило, рекомендуется всегда перекодировать значения после проверки, чтобы избежать этого класса проблем несовпадения синтаксического анализатора.

net/http

Пакет net/http теперь использует новый (*tls.Conn).HandshakeContext с Request контекстом при выполнении рукопожатий TLS на клиенте или сервере.

Установка отрицательного значения для полей Server ReadTimeout или WriteTimeout теперь указывает на отсутствие таймаута, а не на немедленный таймаут.

Функция ReadRequest теперь возвращает ошибку, если запрос содержит несколько заголовков Host.

При создании перенаправления на очищенную версию URL-адреса ServeMux теперь всегда использует относительные URL-адреса в заголовке Location. Раньше он отображал полный URL-адрес запроса, что могло привести к непреднамеренному перенаправлению, если бы клиент мог отправить абсолютный URL-адрес запроса.

При интерпретации определенных заголовков HTTP, обрабатываемых net/http, символы, отличные от ASCII, теперь игнорируются или отклоняются.

Если Request.ParseForm возвращает ошибку при вызове Request.ParseMultipartForm, последний теперь продолжает заполнять Request.MultipartForm перед его возвратом.

net/http/httptest

ResponseRecorder.WriteHeader теперь вызывает панику, если предоставленный код не является допустимым трехзначным кодом состояния HTTP. Это соответствует поведению реализаций ResponseWriter в пакете net/http.

net/url

Новый метод Values.Has сообщает, установлен ли параметр запроса.

os

Метод File.WriteString оптимизирован, чтобы не копировать входную строку.

reflect

Новый метод Value.CanConvert сообщает, можно ли преобразовать значение в тип. Это можно использовать, чтобы избежать паники при преобразовании среза в тип указателя массива, если срез слишком короткий. Раньше для этого было достаточно использовать Type.ConvertibleTo, но недавно разрешенное преобразование из типа среза в тип указателя массива может вызвать панику, даже если типы являются конвертируемыми.

Новые методы StructField.IsExported и Method.IsExported сообщают, экспортируется ли поле структуры или метод типа. Они предоставляют более удобочитаемую альтернативу проверке, пуст ли PkgPath.

Новая функция VisibleFields возвращает все видимые поля в типе структуры, включая поля внутри анонимных членов структуры.

Функция ArrayOf теперь вызывает панику при вызове с отрицательной длиной.

Проверки метода Type.ConvertibleTo больше недостаточно, чтобы гарантировать, что вызов Value.Convert не вызовет панику. Может возникнуть паника при преобразовании`[]T` в `*[N]T`, если длина среза меньше N.

runtime/metrics

Были добавлены новые метрики, отслеживающие общее количество выделенных и освобожденных байтов и объектов. Также была добавлена ​​новая метрика, отслеживающая распределение задержек планирования горутин.

runtime/pprof

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

strconv

Пакет strconv теперь использует алгоритм Ryū Ульфа Адамса для форматирования чисел с плавающей запятой. Этот алгоритм улучшает производительность на большинстве входов и более чем на 99% быстрее на входах наихудшего случая.

Новая функция QuotedPrefix возвращает строку в кавычках (в понимании Unquote) в начале ввода.

strings

Метод Builder.WriteRune теперь записывает заменяющий символ U+FFFD для отрицательных значений рун, как и для других недопустимых рун.

sync/atomic

atomic.Value теперь имеет методы Swap и CompareAndSwap, которые обеспечивают дополнительные атомарные операции.

syscall

Функции GetQueuedCompletionStatus и PostQueuedCompletionStatus теперь не рекомендуются. Эти функции имеют неправильные сигнатуры и заменены эквивалентами в пакете golang.org/x/sys/windows.

В Unix-подобных системах группа процессов дочернего процесса теперь настроена с заблокированными сигналами. Это позволяет избежать отправки SIGTTOU дочернему элементу, когда родитель находится в группе фоновых процессов.

Версия SysProcAttr для Windows имеет два новых поля. AdditionalInheritedHandles - это список дополнительных дескрипторов, которые будут унаследованы новым дочерним процессом. ParentProcess позволяет указать родительский процесс для нового процесса.

Константа MSG_CMSG_CLOEXEC теперь определена в DragonFly и всех системах OpenBSD (она уже была определена в некоторых системах OpenBSD и во всех системах FreeBSD, NetBSD и Linux).

Константы SYS_WAIT6 и WEXITED теперь определены в системах NetBSD (SYS_WAIT6 уже был определен в системах DragonFly и FreeBSD; WEXITED уже был определен в системах Darwin, DragonFly, FreeBSD, Linux и Solaris).

testing

Добавлен новый флаг тестирования -shuffle, который контролирует порядок выполнения тестов и бенчмарков.

Новые методы T.Setenv и B.Setenv поддерживают установку переменной среды на время теста или бенчмарка.

text/template/parse

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

time

Тип Time теперь имеет метод GoString, который будет возвращать более полезное значение для времени при печати с помощью спецификатора формата %#v в пакете fmt.

Новый метод Time.IsDST можно использовать для проверки того, соответствует ли время летнему времени в его настроенном месте.

Новые методы Time.UnixMilli и Time.UnixMicro возвращают количество миллисекунд и микросекунд, прошедших с 1 января 1970 года по всемирному координированному времени соответственно.

Новые функции UnixMilli и UnixMicro возвращают местное время, соответствующее заданному времени Unix.

Пакет теперь принимает запятую "," в качестве разделителя дробных секунд при синтаксическом анализе и форматировании времени. Например, теперь принимаются следующие схемы времени:

2006-01-02 15:04:05,999999999 -0700 MST
Mon Jan _2 15:04:05,000000 2006
Monday, January 2 15:04:05,000 2006

Новый константа Layout определяет опорное время.

unicode

Функции Is, IsGraphic, IsLetter, IsLower, IsMark, IsNumber, IsPrint, IsPunct, IsSpace, IsSymbol и IsUpper теперь возвращают false при отрицательных значениях рун, как и для других недопустимых рун.


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


Купить gopher

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

Трюки со срезами в Golang

Фильтрация без аллокации

Этот трюк использует тот факт, что срез имеет тот же подлежащий массив и емкость, что и оригинал, поэтому хранилище повторно используется для отфильтрованного среза. Конечно, исходное содержимое изменено.

b := a[:0]
for _, x := range a {
    if f(x) {
        b = append(b, x)
    }
}

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

for i := len(b); i < len(a); i++ {
    a[i] = nil // или нулевое значение T
}

Расположение содержимого среза в обратном порядке

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

for i := len(a)/2-1; i >= 0; i-- {
    opp := len(a)-1-i
    a[i], a[opp] = a[opp], a[i]
}

То же самое, только с двумя индексами:

for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
    a[left], a[right] = a[right], a[left]
}

Перемешивание содержимого среза

Алгоритм Фишера – Йейтса:

Начиная с Go 1.10, это доступно по адресу math/rand.Shuffle

for i := len(a) - 1; i > 0; i-- {
    j := rand.Intn(i + 1)
    a[i], a[j] = a[j], a[i]
}

Пакетирование (создание батчей) с минимальным выделением ресурсов

Полезно, если вы хотите выполнять пакетную обработку больших срезов.

actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
batchSize := 3
batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize)

for batchSize < len(actions) {
    actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
}
batches = append(batches, actions)

Дает следующее:

[[0 1 2] [3 4 5] [6 7 8] [9]]

Дедупликация на месте (сопоставимая)

import "sort"

in := []int{3,2,1,4,3,2,1,4,1} // любой элемент можно отсортировать
sort.Ints(in)
j := 0
for i := 1; i < len(in); i++ {
	if in[j] == in[i] {
		continue
	}
	j++
	// сохраняем исходные данные
	// in[i], in[j] = in[j], in[i]
	// устанавливаем только то, что требуется
	in[j] = in[i]
}
result := in[:j+1]
fmt.Println(result) // [1 2 3 4]

Перемещение элемента на передний план или вставка, если отсуствует

// moveToFront перемещает needle в начало среза
func moveToFront(needle string, haystack []string) []string {
    if len(haystack) != 0 && haystack[0] == needle {
        return haystack
    }
    prev := needle
    for i, elem := range haystack {
        switch {
        case i == 0:
            haystack[0] = needle
            prev = elem
        case elem == needle:
            haystack[i] = prev
            return haystack
        default:
            haystack[i] = prev
            prev = elem
        }
    }
    return append(haystack, prev)
}

haystack := []string{"a", "b", "c", "d", "e"} // [a b c d e]
haystack = moveToFront("c", haystack)         // [c a b d e]
haystack = moveToFront("f", haystack)         // [f c a b d e]

Раздвижное окно

func slidingWindow(size int, input []int) [][]int {
    // возвращает входной срез как первый элемент
    if len(input) <= size {
        return [][]int{input}
    }

    // выделяем срез точного размера, который нам нужен
    r := make([][]int, 0, len(input)-size+1)

    for i, j := 0, size; j <= len(input); i, j = i+1, j+1 {
        r = append(r, input[i:j])
    }

    return r
}

a:=[]int{1,2,3,4,5}
aa := slidingWindow(3, a)
fmt.Println(aa) // [[1 2 3] [2 3 4] [3 4 5]]


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