如何在gorm和sqlite3中使用pgtype.Numeric?

huangapple go评论79阅读模式
英文:

How to use pgtype.Numeric with gorm and sqlite3?

问题

我需要使用gORM存储非常大且高精度的数字,并且使用pgtype.Numeric似乎是最好的选择。然而,我无法这样做,因为我遇到了一个错误:sql: Scan error on column index 4, name "foo": cannot scan int64

我的模型大致如下:

type Model struct {
    gorm.Model
    Foo *pgtype.Numeric `gorm:"not null"`
}

我不确定是否使用pgtype.Numeric是最好的选择(这是我看到其他人使用的),或者我做错了什么。谢谢!

引发错误的代码:

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
  "math/big"

  "github.com/jackc/pgtype"
)

type Model struct {
  gorm.Model
  Foo *pgtype.Numeric `gorm:"not null"`
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }
  // 迁移模式
  db.AutoMigrate(&Model{})

  // 创建记录
  db.Create(&Model{Foo: &pgtype.Numeric{Int: big.NewInt(10000000), Status: pgtype.Present}})

  var m Model
  db.First(&m) // 这一行引发了错误
}
英文:

I need to store very large and high precision numbers with gORM, and using a pgtype.Numeric seems like the best bet. However, I cannot because I get an error: sql: Scan error on column index 4, name "foo": cannot scan int64

My model looks something like this:

type Model struct {
    gorm.Model
    Foo *pgtype.Numeric `gorm:"not null"`
}

Not sure if using pgtype.Numeric is the best (that's what i've seen everyone else use), or I'm doing something wrong. Thanks!

The code that caused the error:

package main

import (
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
  "math/big"

  "github.com/jackc/pgtype"
)

type Model struct {
  gorm.Model
  Foo *pgtype.Numeric `gorm:"not null"`
}

func main() {
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }
  // Migrate the schema
  db.AutoMigrate(&Model{})

  // Create
  db.Create(&Model{Foo: &pgtype.Numeric{Int: big.NewInt(10000000), Status: pgtype.Present}})

  var m Model
  db.First(&m) // this line causes the error
}

答案1

得分: 1

Sqlite3不支持大整数,因此无法直接实现。我运行了代码,foo列被创建为:

`foo` numeric NOT NULL

在sqlite中,https://www.sqlite.org/datatype3.html,这意味着:

> 具有NUMERIC亲和性的列可以包含使用所有五个存储类的值... 如果TEXT值是一个格式正确的整数字面值,但太大而无法适应64位有符号整数,则会转换为REAL。

因此,您的大整数将转换为float64。好在它引发了错误而不是悄无声息地丢失精度。

您可以将大整数先转换为字符串或字节,然后存储。

英文:

Sqlite3 does not support big integer so there is no way you can accomplish that directly. I run the code and foo column is create as:

`foo` numeric NOT NULL

Which in sqlite https://www.sqlite.org/datatype3.html means

> A column with NUMERIC affinity may contain values using all five storage classes... If the TEXT value is a well-formed integer literal that is too large to fit in a 64-bit signed integer, it is converted to REAL.

So your big int will turn into float64. Good thing it paniced instead of losing accuracy silently.

What you can do is convert the big int to string or bytes first and store that.

答案2

得分: 0

在调试数据库反序列化的sql.Scanner接口时,可以注意到从数据库中获取的值要么是int64类型,要么是float64类型。这导致了相应的错误消息。

一种可能的解决方案是在数据库中使用text数据类型,通过在字段标签中添加text类型:

`gorm:"type:text;"`

使用github.com/shopspring/decimal包,可以使用NewString函数方便地创建一个十进制数。

插入数据的修改后的代码如下:

num, err := decimal.NewFromString("123456789012345.12345678901")
if err != nil {
    panic(err)
}
db.Create(&Model{Foo: &num})

模型结构可能如下所示:

type Model struct {
    gorm.Model
    Foo *decimal.Decimal `gorm:"not null;type:text;"`
}

这将导致以下模式:

如何在gorm和sqlite3中使用pgtype.Numeric?

测试

如果在decimal.Scan中插入一个断点,可以看到从数据库中预期地以字符串形式获取的值,从而使用NewFromString创建了一个十进制数(参见Decimal的scan方法)。

如果在主函数的末尾添加以下代码:

fmt.Println(m.Foo)

在调试控制台中将输出以下内容:

123456789012345.12345678901

完整程序

稍作修改以适应上述要点后,完整的程序如下所示:

package main

import (
    "fmt"
    "github.com/shopspring/decimal"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type Model struct {
    gorm.Model
    Foo *decimal.Decimal `gorm:"not null;type:text;"`
}

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    // 迁移模式
    db.AutoMigrate(&Model{})

    // 创建
    num, err := decimal.NewFromString("123456789012345.12345678901")
    if err != nil {
        panic(err)
    }
    db.Create(&Model{Foo: &num})

    var m Model
    db.First(&m)
    fmt.Println(m.Foo)
}

pgtype.Numeric和SQLite

如果使用PostgreSQL数据库,gorm可以与pgtype.Numeric一起使用来处理类似于123456789012345.12345678901的十进制数。只需在Postgres端使用适当的所需精度(例如numeric(50,15))使用numeric数据类型即可。

毕竟,这正是pgtype的用途,参见pgtype自述文件中的说明:
>pgtype是https://github.com/jackc/pgx PostgreSQL驱动程序的类型系统。

然而,如果出于上述原因在SQLite中使用文本数据类型,则pgtype.Numeric将无法与SQLite一起使用。尝试使用上述数字时,会将12345678901234512345678901e-11写入数据库,并在读取时出现以下错误:

sql: Scan error on column index 4, name "foo": 12345678901234512345678901e-11 is not a number
英文:

When debugging the sql.Scanner interface for database deserialization, it is noticeable that the value from the database arrives either as int64 or float64. This then leads to the corresponding error message.

A possible solution is to use a text data type in the database, by adding the type text to the field tag:

`gorm: "type:text;"` 

Using the github.com/shopspring/decimal package, you can conveniently create a decimal number using the NewString function.

The adapted code to insert the data:

num, err := decimal.NewFromString("123456789012345.12345678901")
if err != nil {
	panic(err)
}
db.Create(&Model{Foo: &num})

The model structure might then look something like this:

type Model struct {
	gorm.Model
	Foo *decimal.Decimal `gorm: "not null;type:text;"`
}

This would result in the following schema:

如何在gorm和sqlite3中使用pgtype.Numeric?

Test

If one inserts a breakpoint in decimal.Scan, one can see that the value comes from the database as expected as a string, resulting in the creation of a decimal with NewFromString (see Decimal's scan method).

If you add this line of code to the end of the main function

fmt.Println(m.Foo)

it would result in the following output in the debug console:

123456789012345.12345678901

Complete Program

Your complete program, slightly adapted to the above points, would then look something like this:

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type Model struct {
	gorm.Model
	Foo *decimal.Decimal `gorm:"not null;type:text;"`
}

func main() {
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	// Migrate the schema
	db.AutoMigrate(&Model{})

	// Create
	num, err := decimal.NewFromString("123456789012345.12345678901")
	if err != nil {
		panic(err)
	}
	db.Create(&Model{Foo: &num})

	var m Model
	db.First(&m)
	fmt.Println(m.Foo)
}

pgtype.Numeric and SQLite

If a PostgreSQL database is used, gorm can be used together with pgtype.Numeric to handle decimal numbers like 123456789012345.12345678901. You just need to use the numeric data type on the Postgres side with the appropriate desired precision (e.g. numeric(50,15)).

After all, this is exactly what pgtype is for, see the pgtype readme where it says:
>pgtype is the type system underlying the https://github.com/jackc/pgx PostgreSQL driver.

However, if you use a text data type in SQLite for the reasons mentioned above, pgtype.Numeric will not work with SQLite. An attempt with the above number writes 12345678901234512345678901e-11 to the DB and when reading it out the following error occurs:

sql: Scan error on column index 4, name "foo": 12345678901234512345678901e-11 is not a number

huangapple
  • 本文由 发表于 2022年12月6日 11:39:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/74697000.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定