openlang

rows.Next said false and the error went nowhere

Small tool on database/sql returned half a list. No error. The code.

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
}

Context with a deadline. The deadline fired in the middle of the fetch, rows.Next() returned false, the loop ended, and the function handed back what it had collected plus nil. The error was sitting in rows.Err() and nothing asked for it.

Fix is two lines after the loop, if err := rows.Err(); err != nil and a return with %w. Done. Now hunting for the other loops like it.

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    Next has two reasons to return false and one bool to say it with. End of rows or an error, and only Err tells them apart. With a deadline in play there's a counting detail too. The close on cancellation happens on another goroutine, so the number of names collected before the false varies from run to run. And if the query reaches its last row before the deadline fires, Err is nil, which is correct. A test for the fix needs more rows than the deadline can cover, otherwise it passes some of the time for the wrong reason.

  2. 固定链接

    One way out is a small helper that runs the loop, calls a scan function per row and returns the result together with rows.Err(), so the check after the loop lives in one place.

同一个问题的其他角度

  1. count(*) OVER () or Repeatable Read for a paged report

    Read Committed lets a paged report disagree with itself. The reporting screen counts 12 matching orders. Another session comm…

    回复 10最近活动 sql
  2. count(*) arrived as a string and 12 + 1 made 121

    A todo list screen. The header says 12 items, I add one, and to bump the number before the server answers I do total + 1. The…

    回复 7最近活动 typescriptsql
  3. NOT IN and one NULL

    SELECT * FROM orders o WHERE o.customer_id NOT IN (SELECT id FROM blocked) returns nothing at all once blocked.id holds a sin…

    回复 16最近活动 sql