It makes no sense to retrieve the same OIDs each and every connection, because they are not going to change unless the database is reset, something it is very unlikely to happen in production. Thus, it is best to query them the first time the application connects to the database, that it is done at startup to query the available languages, and then reuse the OIDs. I can get away of using an “unprotected” map, instead of sync.Map or a map in tandem with sync.RWMutex, because the application establishes a connection at startup from a single goroutine, and it registers _all_ types we will need to register within the application’s lifespan, hence it there will be no more writes to that map once the web server is listening for incoming connections. This is risky, however, and i hope i do not have to regret it.
342 lines
9.3 KiB
Go
342 lines
9.3 KiB
Go
package pkg
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/jackc/pgtype"
|
|
"github.com/jackc/pgx/v4"
|
|
)
|
|
|
|
var (
|
|
oidCache = make(map[string]uint32)
|
|
)
|
|
|
|
type CustomerTaxDetails struct {
|
|
BusinessName string
|
|
VATIN string
|
|
Address string
|
|
City string
|
|
Province string
|
|
PostalCode string
|
|
CountryCode string
|
|
}
|
|
|
|
func (src CustomerTaxDetails) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
|
typeName := "tax_details"
|
|
dt, ok := ci.DataTypeForName(typeName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
|
|
}
|
|
values := []interface{}{
|
|
src.BusinessName,
|
|
src.VATIN,
|
|
src.Address,
|
|
src.City,
|
|
src.Province,
|
|
src.PostalCode,
|
|
src.CountryCode,
|
|
}
|
|
ct := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
|
|
if err := ct.Set(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return ct.EncodeBinary(ci, buf)
|
|
}
|
|
|
|
type NewInvoiceProductArray []*invoiceProductForm
|
|
|
|
func (src NewInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
|
typeName := "new_invoice_product[]"
|
|
dt, ok := ci.DataTypeForName(typeName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
|
|
}
|
|
var values [][]interface{}
|
|
for _, form := range src {
|
|
var productId interface{} = form.ProductId.Val
|
|
if form.ProductId.Val == "" {
|
|
productId = nil
|
|
}
|
|
values = append(values, []interface{}{
|
|
productId,
|
|
form.Name.Val,
|
|
form.Description.Val,
|
|
form.Price.Val,
|
|
form.Quantity.Val,
|
|
form.Discount.Float64() / 100.0,
|
|
form.Tax.Selected,
|
|
})
|
|
}
|
|
array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
|
|
if err := array.Set(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return array.EncodeBinary(ci, buf)
|
|
}
|
|
|
|
type EditedInvoiceProductArray []*invoiceProductForm
|
|
|
|
func (src EditedInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
|
typeName := "edited_invoice_product[]"
|
|
dt, ok := ci.DataTypeForName(typeName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
|
|
}
|
|
var values [][]interface{}
|
|
for _, form := range src {
|
|
values = append(values, []interface{}{
|
|
form.InvoiceProductId.IntegerOrNil(),
|
|
form.ProductId.IntegerOrNil(),
|
|
form.Name.Val,
|
|
form.Description.Val,
|
|
form.Price.Val,
|
|
form.Quantity.Val,
|
|
form.Discount.Float64() / 100.0,
|
|
form.Tax.Selected,
|
|
})
|
|
}
|
|
array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
|
|
if err := array.Set(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return array.EncodeBinary(ci, buf)
|
|
}
|
|
|
|
type NewQuoteProductArray []*quoteProductForm
|
|
|
|
func (src NewQuoteProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
|
typeName := "new_quote_product[]"
|
|
dt, ok := ci.DataTypeForName(typeName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
|
|
}
|
|
var values [][]interface{}
|
|
for _, form := range src {
|
|
var productId interface{} = form.ProductId.Val
|
|
if form.ProductId.Val == "" {
|
|
productId = nil
|
|
}
|
|
values = append(values, []interface{}{
|
|
productId,
|
|
form.Name.Val,
|
|
form.Description.Val,
|
|
form.Price.Val,
|
|
form.Quantity.Val,
|
|
form.Discount.Float64() / 100.0,
|
|
form.Tax.Selected,
|
|
})
|
|
}
|
|
array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
|
|
if err := array.Set(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return array.EncodeBinary(ci, buf)
|
|
}
|
|
|
|
type EditedQuoteProductArray []*quoteProductForm
|
|
|
|
func (src EditedQuoteProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
|
typeName := "edited_quote_product[]"
|
|
dt, ok := ci.DataTypeForName(typeName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
|
|
}
|
|
var values [][]interface{}
|
|
for _, form := range src {
|
|
values = append(values, []interface{}{
|
|
form.QuoteProductId.IntegerOrNil(),
|
|
form.ProductId.IntegerOrNil(),
|
|
form.Name.Val,
|
|
form.Description.Val,
|
|
form.Price.Val,
|
|
form.Quantity.Val,
|
|
form.Discount.Float64() / 100.0,
|
|
form.Tax.Selected,
|
|
})
|
|
}
|
|
array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
|
|
if err := array.Set(values); err != nil {
|
|
return nil, err
|
|
}
|
|
return array.EncodeBinary(ci, buf)
|
|
}
|
|
|
|
func registerPgTypes(ctx context.Context, conn *pgx.Conn) error {
|
|
if _, err := conn.Exec(ctx, "set role to admin"); err != nil {
|
|
return err
|
|
}
|
|
tagNameOID, err := registerPgType(ctx, conn, &pgtype.Text{}, "tag_name")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tagNameArray := pgtype.NewArrayType("tag_name[]", tagNameOID, func() pgtype.ValueTranscoder {
|
|
return &pgtype.Text{}
|
|
})
|
|
if _, err := registerPgType(ctx, conn, tagNameArray, tagNameArray.TypeName()); err != nil {
|
|
return err
|
|
}
|
|
discountRateOID, err := registerPgType(ctx, conn, &pgtype.Numeric{}, "discount_rate")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newInvoiceProduct, err := pgtype.NewCompositeType(
|
|
"new_invoice_product",
|
|
[]pgtype.CompositeTypeField{
|
|
{"product_id", pgtype.Int4OID},
|
|
{"name", pgtype.TextOID},
|
|
{"description", pgtype.TextOID},
|
|
{"price", pgtype.TextOID},
|
|
{"quantity", pgtype.Int4OID},
|
|
{"discount_rate", discountRateOID},
|
|
{"tax", pgtype.Int4ArrayOID},
|
|
},
|
|
conn.ConnInfo(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newInvoiceProductOID, err := registerPgType(ctx, conn, newInvoiceProduct, newInvoiceProduct.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newInvoiceProductArray := pgtype.NewArrayType("new_invoice_product[]", newInvoiceProductOID, func() pgtype.ValueTranscoder {
|
|
value := newInvoiceProduct.NewTypeValue()
|
|
return value.(pgtype.ValueTranscoder)
|
|
})
|
|
_, err = registerPgType(ctx, conn, newInvoiceProductArray, newInvoiceProductArray.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
editedInvoiceProduct, err := pgtype.NewCompositeType(
|
|
"edited_invoice_product",
|
|
[]pgtype.CompositeTypeField{
|
|
{"invoice_product_id", pgtype.Int4OID},
|
|
{"product_id", pgtype.Int4OID},
|
|
{"name", pgtype.TextOID},
|
|
{"description", pgtype.TextOID},
|
|
{"price", pgtype.TextOID},
|
|
{"quantity", pgtype.Int4OID},
|
|
{"discount_rate", discountRateOID},
|
|
{"tax", pgtype.Int4ArrayOID},
|
|
},
|
|
conn.ConnInfo(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
editedInvoiceProductOID, err := registerPgType(ctx, conn, editedInvoiceProduct, editedInvoiceProduct.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
editedInvoiceProductArray := pgtype.NewArrayType("edited_invoice_product[]", editedInvoiceProductOID, func() pgtype.ValueTranscoder {
|
|
value := editedInvoiceProduct.NewTypeValue()
|
|
return value.(pgtype.ValueTranscoder)
|
|
})
|
|
_, err = registerPgType(ctx, conn, editedInvoiceProductArray, editedInvoiceProductArray.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newQuoteProduct, err := pgtype.NewCompositeType(
|
|
"new_quote_product",
|
|
[]pgtype.CompositeTypeField{
|
|
{"product_id", pgtype.Int4OID},
|
|
{"name", pgtype.TextOID},
|
|
{"description", pgtype.TextOID},
|
|
{"price", pgtype.TextOID},
|
|
{"quantity", pgtype.Int4OID},
|
|
{"discount_rate", discountRateOID},
|
|
{"tax", pgtype.Int4ArrayOID},
|
|
},
|
|
conn.ConnInfo(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newQuoteProductOID, err := registerPgType(ctx, conn, newQuoteProduct, newQuoteProduct.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newQuoteProductArray := pgtype.NewArrayType("new_quote_product[]", newQuoteProductOID, func() pgtype.ValueTranscoder {
|
|
value := newQuoteProduct.NewTypeValue()
|
|
return value.(pgtype.ValueTranscoder)
|
|
})
|
|
_, err = registerPgType(ctx, conn, newQuoteProductArray, newQuoteProductArray.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
editedQuoteProduct, err := pgtype.NewCompositeType(
|
|
"edited_quote_product",
|
|
[]pgtype.CompositeTypeField{
|
|
{"quote_product_id", pgtype.Int4OID},
|
|
{"product_id", pgtype.Int4OID},
|
|
{"name", pgtype.TextOID},
|
|
{"description", pgtype.TextOID},
|
|
{"price", pgtype.TextOID},
|
|
{"quantity", pgtype.Int4OID},
|
|
{"discount_rate", discountRateOID},
|
|
{"tax", pgtype.Int4ArrayOID},
|
|
},
|
|
conn.ConnInfo(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
editedQuoteProductOID, err := registerPgType(ctx, conn, editedQuoteProduct, editedQuoteProduct.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
editedQuoteProductArray := pgtype.NewArrayType("edited_quote_product[]", editedQuoteProductOID, func() pgtype.ValueTranscoder {
|
|
value := editedQuoteProduct.NewTypeValue()
|
|
return value.(pgtype.ValueTranscoder)
|
|
})
|
|
_, err = registerPgType(ctx, conn, editedQuoteProductArray, editedQuoteProductArray.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
countryCodeOID, err := registerPgType(ctx, conn, &pgtype.Text{}, "country_code")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
taxDetailsType, err := pgtype.NewCompositeType(
|
|
"tax_details",
|
|
[]pgtype.CompositeTypeField{
|
|
{"business_name", pgtype.TextOID},
|
|
{"vatin", pgtype.TextOID},
|
|
{"address", pgtype.TextOID},
|
|
{"city", pgtype.TextOID},
|
|
{"province", pgtype.TextOID},
|
|
{"postal_code", pgtype.TextOID},
|
|
{"discount_rate", countryCodeOID},
|
|
},
|
|
conn.ConnInfo(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = registerPgType(ctx, conn, taxDetailsType, taxDetailsType.TypeName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = conn.Exec(ctx, "reset role")
|
|
return err
|
|
}
|
|
|
|
func registerPgType(ctx context.Context, conn *pgx.Conn, value pgtype.Value, name string) (oid uint32, err error) {
|
|
var found bool
|
|
if oid, found = oidCache[name]; !found {
|
|
if err = conn.QueryRow(ctx, "select $1::regtype::oid", name).Scan(&oid); err != nil {
|
|
return
|
|
}
|
|
oidCache[name] = oid
|
|
}
|
|
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: value, Name: name, OID: oid})
|
|
return
|
|
}
|