Go SQL-запрос несоответствие
Я испытываю некоторые действительно странные несоответствия при выполнении запросов, и мне было интересно, если кто-нибудь знает, почему.
Представьте, что у меня есть структура, определенная следующим образом:
type Result struct {
Afield string `db:"A"`
Bfield interface{} `db:"B"`
Cfield string `db:"C"`
Dfield string `db:"D"`
}
И таблица MySQL со следующими столбцами:
A : VARCHAR(50)
B : INT
C : VARCHAR(50)
D : VARCHAR(50)
Запрос, который я хотел бы выполнить:
ВЫБЕРИТЕ A, B, C, D ИЗ таблицы ГДЕ A = "a"
Первый способ это может быть выполнено:
db.Get(&result, `SELECT A, B, C, D FROM table WHERE A="a"`)
Вторым способом это можно выполнить:
db.Get(&result, `SELECT A, B, C, D FROM table WHERE A=?`, "a")
Несоответствия, с которыми я сталкиваюсь, следующие: при первом выполнении запроса тип Bfield: int
, Тем не менее, при выполнении запроса во второй раз, это []uint8
,
Этот результат происходит, например, когда B равен 1.
Почему тип Bfield отличается в зависимости от того, как выполняется запрос?
декларация соединения:
// Connection is an interface for making queries.
type Connection interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Get(dest interface{}, query string, args ...interface{}) error
Select(dest interface{}, query string, args ...interface{}) error
}
РЕДАКТИРОВАТЬ
Это также происходит с использованием пакета Go database / sql package + driver. Запросы ниже присваивают Bfield
в []uint8
а также int64
соответственно.
дБ имеет тип * sql.DB
запрос 1:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A="a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
-> тип Bfield
является []uint8
запрос 2:
db.QueryRow(SELECT A, B, C, D FROM table WHERE A=?, "a").Scan(&result.Afield, &result.Bfield, &result.Cfield, &result.Dfield)
-> тип Bfield
является int64
РЕДАКТИРОВАТЬ
Что еще нужно отметить при объединении нескольких предложений WHERE, если хотя бы 1 заполнено с помощью ?
запрос вернется int
, В противном случае, если они все заполнены в строке, он вернет []uint8
2 ответа
Краткий ответ: потому что драйвер MySQL использует другой протокол для запросов с параметрами и без параметров. Используйте подготовленное утверждение, чтобы получить последовательные результаты.
Следующее объяснение относится к стандартному драйверу MySQL https://github.com/go-sql-driver/mysql/tree/v1.4
В первом случае драйвер отправляет запрос непосредственно в MySQL и интерпретирует результат как *textRows
структура. Эта структура (почти) всегда декодирует результаты в байтовый фрагмент и оставляет преобразование в лучший тип для Go sql
пакет. Это прекрасно работает, если пункт назначения int
, string
, sql.Scanner
и т. д., но не для interface{}
,
Во втором случае драйвер обнаруживает наличие аргументов и возвращает driver.ErrSkip
, Это заставляет пакет Go SQL использовать PreparedStatement. И в этом случае драйвер MySQL использует *binaryRows
структура для интерпретации результатов. Эта структура использует объявленный тип столбца ( INT
в этом случае) декодировать значение, в этом случае декодировать значение в int64
,
Интересный факт: если вы предоставите interpolateParams=true
параметр для базы данных DSN (например, "root:testing@/mysql?interpolateParams=true"
), драйвер MySQL подготовит запрос на стороне клиента и не будет использовать PreparedStatement. На этом этапе оба типа запросов ведут себя одинаково.
Небольшое подтверждение концепции:
package main
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
type Result struct {
Afield string
Bfield interface{}
}
func main() {
db, err := sql.Open("mysql", "root:testing@/mysql")
if err != nil {
log.Fatal(err)
}
defer db.Close()
if _, err = db.Exec(`CREATE TABLE IF NOT EXISTS mytable(A VARCHAR(50), B INT);`); err != nil {
log.Fatal(err)
}
if _, err = db.Exec(`DELETE FROM mytable`); err != nil {
log.Fatal(err)
}
if _, err = db.Exec(`INSERT INTO mytable(A, B) VALUES ('a', 3)`); err != nil {
log.Fatal(err)
}
var (
usingLiteral Result
usingParam Result
usingLiteralPrepared Result
)
row := db.QueryRow(`SELECT B FROM mytable WHERE A='a'`)
if err := row.Scan(&usingLiteral.Bfield); err != nil {
log.Fatal(err)
}
row = db.QueryRow(`SELECT B FROM mytable WHERE A=?`, "a")
if err := row.Scan(&usingParam.Bfield); err != nil {
log.Fatal(err)
}
stmt, err := db.Prepare(`SELECT B FROM mytable WHERE A='a'`)
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
row = stmt.QueryRow()
if err := row.Scan(&usingLiteralPrepared.Bfield); err != nil {
log.Fatal(err)
}
log.Printf("Type when using literal: %T", usingLiteral.Bfield) // []uint8
log.Printf("Type when using param: %T", usingParam.Bfield) // int64
log.Printf("Type when using prepared: %T", usingLiteralPrepared.Bfield) // int64
}
Ваша первая строка SQL в MySql неоднозначна и может иметь слишком большое значение, как описано в Stackru по следующему адресу
Когда использовать одинарные, двойные и обратные тики в MySQL
В зависимости от режима SQL, ваша команда SQL может интерпретироваться как
SELECT A, B, C, D FROM table WHERE A='a'
это то, что я думаю, вы ожидаете.
или как
SELECT A, B, C, D FROM table WHERE A=`a`
Чтобы избежать этой неоднозначности, можете ли вы сделать новый ПЕРВЫЙ тест для замены двойных кавычек одинарными?
Если такое же поведение сохраняется, мой ответ не является хорошим ответом.
Если ОБА SQL выбрать возвращаемое значение, ваш вопрос был решен.
Используя `символ, вы передаете имя переменной, а не строковое значение!