Go несоответствие SQL-запросов - PullRequest
0 голосов
/ 22 февраля 2019

Я испытываю некоторые действительно странные несоответствия при выполнении запросов, и мне было интересно, если бы кто-нибудь знал почему.

Представьте, что у меня есть структура, определенная следующим образом:

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 ИЗ таблицы WHERE 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.

Почему тип поля B отличается в зависимостио том, как выполняется запрос?

объявление соединения:

// 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
}

EDIT

Это также происходит с помощью базы данных Go /Пакет SQL + драйвер.Приведенные ниже запросы присваивают 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

EDIT

Что еще нужно отметить при объединении в цепочку нескольких предложений WHERE, если не менее 1 заполняется с помощью ?, запрос вернет int.В противном случае, если они все заполнены в строке, он вернет []uint8

Ответы [ 2 ]

0 голосов
/ 26 февраля 2019

Краткий ответ: потому что драйвер MySQL использует другой протокол для запросов с параметрами и без них.Используйте подготовленное утверждение, чтобы получить последовательные результаты.

Следующее объяснение относится к стандартному драйверу MySQL github.com / go-sql-driver / mysql, версия 1.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
}
0 голосов
/ 25 февраля 2019

Ваша первая строка SQL в MySql неоднозначна и может иметь слишком большое значение, как описано в StackOverflow по следующему адресу

Когда использовать одинарные кавычки, двойные кавычки и обратные тики в MySQL

В зависимости от режима SQL, ваша команда SQL может интерпретироваться как

SELECT A, B, C, D FROM table WHERE A='a'

, что, как я думаю, вы ожидаете.

или

SELECT A, B, C, D FROM table WHERE A=`a`

Чтобы избежать этой неоднозначности, можете ли вы сделать новый ПЕРВЫЙ тест для замены двойных кавычек на одинарные?

Если такое же поведение сохраняется, мой ответ не является хорошим ответом.

Если ОБА SQL выбрать возвращаемое значение, ваш вопрос был решен.

Используя символ `, вы передаете имя переменной, а не строковое значение!

...