Описание типа Row database/sql
Categories:
type Row
type Row struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Row — результат вызова DB.QueryRow для выбора одной строки.
func (*Row) Err
func (r *Row) Err() error
Err предоставляет способ для обертывающих пакетов проверять ошибки запроса без вызова Row.Scan. Err возвращает ошибку, если таковая имела место при выполнении запроса. Если эта ошибка не равна nil, она также будет возвращена из Row.Scan.
func (*Row) Scan
func (r *Row) Scan(dest ...any) error
Scan копирует столбцы из соответствующей строки в значения, на которые указывает dest. Подробности см. в документации по Rows.Scan. Если запросу соответствует более одной строки, Scan использует первую строку и отбрасывает остальные. Если ни одна строка не соответствует запросу, Scan возвращает ErrNoRows.
type Rows
type Rows struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Rows — это результат запроса. Его курсор начинается перед первой строкой набора результатов. Используйте Rows.Next для перехода от строки к строке.
Пример
package main
import (
"context"
"database/sql"
"log"
"strings"
)
// Глобальные переменные лучше инициализировать
var (
ctx = context.Background() // Инициализация контекста
db *sql.DB
)
func main() {
// На практике db должен быть инициализирован перед использованием
// Например:
// var err error
// db, err = sql.Open("driver-name", "datasource")
// if err != nil {
// log.Fatal(err)
// }
// defer db.Close()
age := 27
// Добавляем проверку, что db не nil
if db == nil {
log.Fatal("database connection is not initialized")
}
// Используем QueryContext вместо Query для явного указания контекста
rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
names := make([]string, 0)
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
log.Fatal(err)
}
names = append(names, name)
}
// Проверяем ошибки после итерации
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// Добавляем проверку на пустой результат
if len(names) == 0 {
log.Printf("No users found with age %d", age)
return
}
log.Printf("%s are %d years old", strings.Join(names, ", "), age)
}
func (*Rows) Close
func (rs *Rows) Close() error
Close закрывает Rows, предотвращая дальнейшее перечисление. Если Rows.Next вызывается и возвращает false, а дальнейших наборов результатов нет, Rows закрывается автоматически, и достаточно проверить результат Rows.Err. Close является идемпотентным и не влияет на результат Rows.Err.
func (*Rows) ColumnTypes
func (rs *Rows) ColumnTypes() ([]*ColumnType, error)
ColumnTypes возвращает информацию о столбцах, такую как тип столбца, длина и возможность принятия нулевого значения. Некоторая информация может быть недоступна из некоторых драйверов.
func (*Rows) Columns
func (rs *Rows) Columns() ([]string, error)
Columns возвращает имена столбцов. Columns возвращает ошибку, если строки закрыты.
func (*Rows) Err
func (rs *Rows) Err() error
Err возвращает ошибку, если таковая возникла во время итерации. Err может быть вызван после явного или неявного Rows.Close.
func (*Rows) Next
func (rs *Rows) Next() bool
Next подготавливает следующую строку результата для чтения с помощью метода Rows.Scan. Он возвращает true в случае успеха или false, если следующей строки результата нет или при ее подготовке произошла ошибка. Для различения этих двух случаев следует обратиться к Rows.Err.
Каждому вызову Rows.Scan, даже первому, должен предшествовать вызов Rows.Next.
Функция Rows.Next()
используется для итерации по строкам результата SQL-запроса.
Пример
Базовый пример использования Rows.Next()
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// Подключение к базе данных
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Выполнение запроса
rows, err := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Итерация по строкам результата
for rows.Next() {
var id int
var name, email string
// Сканирование значений из текущей строки
if err := rows.Scan(&id, &name, &email); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s, Email: %s\n", id, name, email)
}
// Проверка ошибок после итерации
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
Почему используется цикл for?
Цикл for rows.Next()
выполняет итерации по всем строкам результата запроса:
-
Количество итераций равно количеству строк, возвращенных запросом.
-
Механизм работы:
- При первом вызове
rows.Next()
перемещает курсор к первой строке результата - При последующих вызовах перемещает курсор к следующей строке
- Когда строки заканчиваются, возвращает
false
и цикл завершается
- При первом вызове
-
Важно: Каждый вызов
rows.Scan()
должен быть предварен вызовомrows.Next()
Детализированный пример с обработкой ошибок
func getActiveUsers(db *sql.DB) ([]User, error) {
rows, err := db.Query("SELECT id, name, email FROM users WHERE active = ?", true)
if err != nil {
return nil, fmt.Errorf("query failed: %v", err)
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
// Можно продолжить обработку других строк или прервать
return nil, fmt.Errorf("scan failed: %v", err)
}
users = append(users, u)
}
// Проверяем ошибки, которые могли возникнуть во время итерации
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("rows iteration failed: %v", err)
}
return users, nil
}
type User struct {
ID int
Name string
Email string
}
Что происходит внутри цикла?
-
Первая итерация:
rows.Next()
перемещает курсор к первой строке (если она есть)- Возвращает
true
, если строка доступна - Выполняется
rows.Scan()
-
Последующие итерации:
rows.Next()
перемещает курсор к следующей строке- Цикл продолжается, пока есть строки
-
Завершение:
- Когда строки заканчиваются,
rows.Next()
возвращаетfalse
- Цикл прерывается
- Проверяется
rows.Err()
на наличие ошибок
- Когда строки заканчиваются,
Важные нюансы:
- Всегда вызывайте
defer rows.Close()
для освобождения ресурсов - Проверяйте
rows.Err()
после цикла для выявления ошибок итерации - Не используйте
rows.Next()
без последующегоrows.Scan()
- Для пустых результатов цикл не выполнится ни разу
Альтернативный пример с обработкой пустого результата
func printUserCount(db *sql.DB) {
rows, err := db.Query("SELECT COUNT(*) FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// rows.Next() вернет true, даже если только одна строка
if rows.Next() {
var count int
if err := rows.Scan(&count); err != nil {
log.Fatal(err)
}
fmt.Printf("Total users: %d\n", count)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
Этот пример показывает, что даже для запросов, возвращающих одну строку (как COUNT), необходимо использовать rows.Next()
перед rows.Scan()
.
func (*Rows) NextResultSet
func (rs *Rows) NextResultSet() bool
NextResultSet подготавливает следующий набор результатов для чтения. Он сообщает, есть ли дальнейшие наборы результатов, или false, если дальнейших наборов результатов нет или если произошла ошибка при переходе к ним. Для различения этих двух случаев следует обратиться к методу Rows.Err.
После вызова NextResultSet перед сканированием всегда следует вызывать метод Rows.Next. Если есть дальнейшие наборы результатов, они могут не содержать строк в наборе результатов.
Функция NextResultSet()
используется, когда запрос к базе данных возвращает несколько наборов результатов (например, при выполнении хранимых процедур или пакетных запросов).
Пример
Пример с несколькими наборами результатов
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера MySQL
)
func main() {
// Инициализация подключения к базе данных
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Запрос, возвращающий несколько наборов результатов
// (например, хранимая процедура с несколькими SELECT)
query := `
SELECT id, name FROM users WHERE active = 1;
SELECT id, title FROM products WHERE price > 100;
SELECT COUNT(*) FROM orders WHERE status = 'completed';
`
// Выполнение запроса
rows, err := db.QueryContext(context.Background(), query)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Обработка первого набора результатов (пользователи)
fmt.Println("=== Active Users ===")
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// Переход к следующему набору результатов (продукты)
if rows.NextResultSet() {
fmt.Println("\n=== Expensive Products ===")
for rows.Next() {
var id int
var title string
if err := rows.Scan(&id, &title); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Title: %s\n", id, title)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
// Переход к следующему набору результатов (количество заказов)
if rows.NextResultSet() {
fmt.Println("\n=== Completed Orders Count ===")
for rows.Next() {
var count int
if err := rows.Scan(&count); err != nil {
log.Fatal(err)
}
fmt.Printf("Completed orders: %d\n", count)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
}
// Проверяем, есть ли еще наборы результатов
if rows.NextResultSet() {
fmt.Println("\nThere are more result sets available")
} else {
fmt.Println("\nNo more result sets")
}
}
Ключевые моменты работы с NextResultSet():
-
Порядок обработки: Всегда сначала обрабатывайте текущий набор результатов с помощью
Next()
иScan()
, прежде чем переходить к следующему. -
Проверка наличия результатов:
NextResultSet()
возвращаетtrue
, если есть следующий набор результатов, даже если он пустой. -
Обработка ошибок: После
NextResultSet()
всегда проверяйтеrows.Err()
на наличие ошибок. -
Пустые наборы: Набор результатов может не содержать строк - это нормально, просто
Next()
вернетfalse
сразу.
Альтернативный пример с хранимой процедурой
func callMultiResultStoredProcedure(db *sql.DB) error {
// Вызов хранимой процедуры, возвращающей несколько наборов результатов
rows, err := db.Query("CALL get_report_data()")
if err != nil {
return err
}
defer rows.Close()
// Обработка всех наборов результатов
resultSetIndex := 0
for {
// Обработка текущего набора результатов
for rows.Next() {
// В зависимости от набора результатов используем разную логику сканирования
switch resultSetIndex {
case 0:
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
return err
}
fmt.Printf("User: %d - %s\n", id, name)
case 1:
var product string
var price float64
if err := rows.Scan(&product, &price); err != nil {
return err
}
fmt.Printf("Product: %s - $%.2f\n", product, price)
// Можно добавить дополнительные case для других наборов
}
}
if err := rows.Err(); err != nil {
return err
}
// Переход к следующему набору результатов
if !rows.NextResultSet() {
break
}
resultSetIndex++
}
return nil
}
Важные предупреждения:
- Не все драйверы баз данных поддерживают несколько наборов результатов.
- Всегда проверяйте
rows.Err()
послеNextResultSet()
. - После перехода к новому набору результатов обязательно вызывайте
rows.Next()
перед сканированием. - Структура данных в каждом наборе результатов может отличаться.
func (*Rows) Scan
func (rs *Rows) Scan(dest ...any) error
Scan копирует столбцы в текущей строке в значения, на которые указывает dest. Количество значений в dest должно совпадать с количеством столбцов в Rows.
Scan преобразует столбцы, прочитанные из базы данных, в следующие общие типы Go и специальные типы, предоставляемые пакетом sql:
*string
*[]byte
*int, *int8, *int16, *int32, *int64
*uint, *uint8, *uint16, *uint32, *uint64
*bool
*float32, *float64
*interface{}
*RawBytes
*Rows (cursor value)
любой тип, реализующий Scanner (см. документацию по Scanner)
В самом простом случае, если тип значения из исходного столбца является целым, bool или строковым типом T, а dest имеет тип *T, Scan просто присваивает значение через указатель.
Scan также преобразует строковые и числовые типы, если при этом не теряется информация. В то время как Scan преобразует все числа, сканированные из числовых столбцов базы данных, в *string, сканирование в числовые типы проверяется на переполнение. Например, float64 со значением 300 или строка со значением «300» могут быть отсканированы в uint16, но не в uint8, хотя float64(255) или «255» могут быть отсканированы в uint8. Исключением является то, что при сканировании некоторых чисел float64 в строки может произойти потеря информации при преобразовании в строку. В общем случае, сканируйте столбцы с плавающей запятой в *float64.
Если аргумент dest имеет тип *[]byte, Scan сохраняет в этом аргументе копию соответствующих данных. Копия принадлежит вызывающему и может быть изменена и храниться неограниченное время. Копирование можно избежать, используя вместо этого аргумент типа *RawBytes; ограничения на его использование см. в документации по RawBytes.
Если аргумент имеет тип *interface{}, Scan копирует значение, предоставленное базовым драйвером, без преобразования. При сканировании из исходного значения типа []byte в *interface{} создается копия среза, и вызывающая сторона владеет результатом.
Исходные значения типа time.Time могут быть отсканированы в значения типа *time.Time, *interface{}, *string или *[]byte. При преобразовании в два последних используется time.RFC3339Nano.
Источниковые значения типа bool могут быть просканированы в типы *bool, *interface{}, *string, *[]byte или *RawBytes.
Для сканирования в *bool источником могут быть true, false, 1, 0 или строковые входы, которые можно проанализировать с помощью strconv.ParseBool.
Scan также может преобразовать курсор, возвращенный из запроса, такого как «select cursor(select * from my_table) from dual»
, в значение *Rows, которое само по себе может быть просканировано. Родительский запрос select закроет любой курсор *Rows, если родительский *Rows закрыт.
Если любой из первых аргументов, реализующих Scanner, возвращает ошибку, эта ошибка будет обернута в возвращаемую ошибку.
type Scanner
type Scanner interface {
// Scan назначает значение из драйвера базы данных.
//
// Значение src будет одного из следующих типов:
//
// int64
// float64
// bool
// []byte
// string
// time.Time
// nil - для значений NULL
//
// Если значение не может быть сохранено
// без потери информации.
//
// Типы ссылок, такие как []byte, действительны только до следующего вызова Scan
// и не должны сохраняться. Их базовая память принадлежит драйверу.
// Если сохранение необходимо, скопируйте их значения перед следующим вызовом Scan.
Scan(src any) error
}
Scanner — интерфейс, используемый Rows.Scan.