суббота, 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

суббота, 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]]


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