openlang

rows.Next вернул false, а ошибка потерялась

Маленькая утилита на database/sql вернула половину списка. Без ошибки. Код такой.

func names(ctx context.Context, db *sql.DB) ([]string, error) {
	rows, err := db.QueryContext(ctx, `SELECT name FROM users ORDER BY id`)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("users: %w", err)
	}
	defer rows.Close()

	var out []string
	for rows.Next() {
		var n string
		if err := rows.Scan(&n); err != nil {
			return nil, fmt.Errorf("scan: %w", err)
		}
		out = append(out, n)
	}
	return out, nil
}

Контекст с дедлайном. Дедлайн истёк посреди выборки, rows.Next() вернул false, цикл кончился, функция отдала то, что успела набрать, и nil. Ошибка лежала в rows.Err(), а его никто не вызывал.

Исправление - две строки после цикла, if err := rows.Err(); err != nil и возврат с %w. Всё. Теперь ищу остальные такие циклы.

Разговор продолжается 3

Следуя доводам
  1. Ссылка на ответ

    У Next две причины вернуть false и один bool, чтобы об этом сказать. Конец строк или ошибка, и различает их только Err. С дедлайном есть ещё деталь со счётом. Закрытие при отмене происходит в другой горутине, поэтому число имён, собранных до false, от запуска к запуску разное. А если запрос успел дойти до последней строки раньше дедлайна, Err равен nil, и это правильно. Тесту на исправление нужно больше строк, чем успевает прочитаться до дедлайна, иначе он иногда проходит не по той причине.

  2. Ссылка на ответ

    Выход - маленький помощник, который крутит цикл, вызывает функцию сканирования на каждую строку и возвращает результат вместе с rows.Err(), так что проверка после цикла живёт в одном месте.

  3. Ссылка на ответ

    Отдельно от ошибки, ветка с sql.ErrNoRows никогда не срабатывает. Эта ошибка приходит из Row.Scan после QueryRow, а Query сообщает об отсутствии строк циклом, который ни разу не выполняется, и nil в ошибке.

Другие стороны того же вопроса

  1. count(*) пришёл строкой, и 12 + 1 дало 121

    Экран списка дел. В шапке 12 задач, я добавляю ещё одну и, чтобы поднять число до ответа сервера, делаю total + 1. В шапке те…

    Ответы 7typescriptsql
  2. UPDATE перепроверяет WHERE после ожидания

    Две сессии под PostgreSQL Read Committed, один счёт с балансом 15, и обе сессии выполняют один и тот же оператор. Сессия A об…

    Ответы 0sql
  3. count(*) OVER () или Repeatable Read для постраничного отчёта

    При Read Committed постраничный отчёт может разойтись сам с собой. Экран отчёта насчитывает 12 подходящих заказов. Другой сеа…

    Ответы 9sql