SQL пропускает первое значение select

#mysql #sql #go

# #mysql #sql #Вперед

Вопрос:

Я не уверен, почему мой код пропускает первую строку, полученную в операторе select . Код:

 func (s *sqlserver) FindAllProducts() (products []*Product, err error) {
    ctx, cancel := getContext()
    defer cancel()

    rows, err := s.QueryContext(ctx,
        "select productid, productname, pricecents, brandname from products")
    if err != nil {
        return
    }   
    defer rows.Close()
    
    for rows.Next() {
        product := new(Product)
        err = rows.Scan(
            amp;product.ProductID,
            amp;product.ProductName,
            amp;product.PriceCents,
            amp;product.BrandName,
            )
        if err != nil {
            return
        }
        products = append(products, product)
    }
    return
}
 

и есть модульный тест, который используется go-sqlmock для обслуживания тестового репозитория:

 func TestProduct(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        require.NoError(t, err)
    }
    s := sqlserver{db}
    defer s.Close()

    product1 := amp;Product{
        1,
        "soda",
        100,
        "prites",
    }
    product2 := amp;Product{
        60,
        "slurpee",
        400,
        "koce",
    }
    product3 := amp;Product{
        21,
        "borg",
        210,
        "ham",
    }

    query := "^insert into *"
    mock.ExpectExec(query).
        WithArgs(product1.ProductID, product1.ProductName, product1.PriceCents, product1.BrandName).
        WillReturnResult(sqlmock.NewResult(0, 1))

    query = "^insert into *"
    mock.ExpectExec(query).
        WithArgs(product2.ProductID, product2.ProductName, product2.PriceCents, product2.BrandName).
        WillReturnResult(sqlmock.NewResult(1, 1))

    query = "^insert into *"
    mock.ExpectExec(query).
        WithArgs(product3.ProductID, product3.ProductName, product3.PriceCents, product3.BrandName).
        WillReturnResult(sqlmock.NewResult(2, 1))

    query = "^select .  from *"
    rows := sqlmock.NewRows([]string{"productid", "productname", "pricecents", "brandname"}).
        AddRow(product1.ProductID, product1.ProductName, product1.PriceCents, product1.BrandName)
    mock.ExpectQuery(query).
        WithArgs(product1.ProductID).
        WillReturnRows(rows)

    query = "^select .  from *"
    rows.AddRow(product2.ProductID, product2.ProductName, product2.PriceCents, product2.BrandName)
    rows.AddRow(product3.ProductID, product3.ProductName, product3.PriceCents, product3.BrandName)
    mock.ExpectQuery(query).
        WillReturnRows(rows)

    err = s.InsertProduct(product1)
    assert.NoError(t, err)

    err = s.InsertProduct(product2)
    assert.NoError(t, err)

    err = s.InsertProduct(product3)
    assert.NoError(t, err)

    product, err := s.FindProductFromID(product1.ProductID)
    assert.NoError(t, err)
    assert.Equal(t, product1, product)

    products, err := s.FindAllProducts()
    assert.NoError(t, err)
    assert.Len(t, products, 3)
    assert.Equal(t, *product1, *products[0])
    assert.Equal(t, *product2, *products[1])
    assert.Equal(t, *product3, *products[2])

    err = mock.ExpectationsWereMet()
    assert.NoError(t, err)
}
 

Вставка продуктов в таблицу работает нормально, но когда я пытаюсь получить продукты, используя s.FindAllProducts первый продукт, он всегда отсутствует в возвращенных продуктах (что приводит assert.Len(t, products, 3) к сбою). Любая помощь очень ценится!

Редактировать: что касается вызова rows.Next() перед сканированием значений в продукт, это то, что говорится в документации, поскольку предполагается, что указатель начинает один индекс перед первой строкой. Однако я отладил код, и проблема, похоже, заключается в том, что указатель начинается на одно место впереди (указывает на первую строку), а вызов rows.Next() перемещает его в следующую строку. Я попытался переместить это утверждение после инициализации продукта, но затем код выдает ошибку: Received unexpected error: sql: Scan called without calling Next

Редактировать 2: похоже, это прямая проблема go-sqlmock , поскольку после тестирования на реальной базе данных SQL база данных корректно возвращает все 3 строки.

Комментарии:

1. Пожалуйста, уточните, где именно ваш тест терпит неудачу? assert.Len(t, products, 3) Сбой, потому что есть только 2 продукта?

2. Я не знаком с Golang, но я предполагаю, что вызов rows.Next() перемещает указатель на вторую строку. Попробуйте сначала создать экземпляр Product и вызвать rows.Next() его последним в цикле.

3.Обратите внимание, что вы должны выполнить проверку ошибок раньше defer rows.Close() . В противном случае ошибка приведет к панике вашей программы, потому rows что будет равно нулю.

4. Также обратите внимание, что ваше регулярное "^insert into *" выражение почти наверняка не означает то, что вы намереваетесь. Это означает «строка, начинающаяся с insert into , за которой следует 0 или более пробелов» 🙂 Это означает, что insert intolerant будет соответствовать, как insert into wheee! Вы, вероятно, имели в виду "^insert into .*" , что может быть сокращено до: ^insert into "

5. Почему у вас есть () в вашем SELECT?, Пожалуйста, удалите их, чтобы получить результаты.

Ответ №1:

Обнаружена ошибка, поскольку rows — указатель по какой-то причине go-sqlmock изменяет набор строк, в результате чего исходные строки в нем удаляются и требуют сброса при каждом WillReturnRows вызове. Следующая часть кода:

     query = "^select .  from *"
    rows.AddRow(product2.ProductID, product2.ProductName, product2.PriceCents, product2.BrandName)
    rows.AddRow(product3.ProductID, product3.ProductName, product3.PriceCents, product3.BrandName)
    mock.ExpectQuery(query).
        WillReturnRows(rows)
 

должно быть изменено, чтобы добавить исходную строку обратно, например:

     query = "^select .  from *"
    rows = sqlmock.NewRows([]string{"productid", "productname", "pricecents", "brandname"})
    rows.AddRow(product1.ProductID, product1.ProductName, product1.PriceCents, product2.BrandName)
    rows.AddRow(product2.ProductID, product2.ProductName, product2.PriceCents, product2.BrandName)
    rows.AddRow(product3.ProductID, product3.ProductName, product3.PriceCents, product3.BrandName)
    mock.ExpectQuery(query).
        WillReturnRows(rows)
 

и тест теперь пройден!