1
0
mirror of https://github.com/ManyakRus/starter.git synced 2025-11-25 23:02:22 +02:00

сделал AfterConnect_NoNull

This commit is contained in:
Nikitin Aleksandr
2025-07-02 14:51:33 +03:00
parent bc2d7aae73
commit ec09cc4ccf
16 changed files with 1058 additions and 29 deletions

4
go.mod
View File

@@ -8,6 +8,7 @@ require (
github.com/cockroachdb/pebble v1.1.2
github.com/denisenkom/go-mssqldb v0.12.3
github.com/dromara/carbon/v2 v2.6.1
github.com/dustin/go-humanize v1.0.1
github.com/emersion/go-imap v1.2.1
github.com/emersion/go-message v0.18.1
github.com/go-faster/errors v0.7.1
@@ -37,6 +38,7 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
golang.org/x/net v0.34.0
google.golang.org/protobuf v1.36.4
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/postgres v1.5.11
@@ -59,7 +61,6 @@ require (
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/getsentry/sentry-go v0.30.0 // indirect
github.com/go-faster/jx v1.1.0 // indirect
@@ -121,7 +122,6 @@ require (
go.opentelemetry.io/otel/trace v1.32.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect

18
go.sum
View File

@@ -66,8 +66,6 @@ github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dromara/carbon/v2 v2.5.2 h1:GquNyA9Imda+LwS9FIzHhKg+foU2QPstH+S3idBRjKg=
github.com/dromara/carbon/v2 v2.5.2/go.mod h1:zyPlND2o27sKKkRmdgLbk/qYxkmmH6Z4eE8OoM0w3DM=
github.com/dromara/carbon/v2 v2.6.1 h1:ExZPeH74ApLJ/nqJ+SGp1JSPFawvTDOCG3WSeqYl0mI=
github.com/dromara/carbon/v2 v2.6.1/go.mod h1:Baj3A1uBBctJmpZWJd6/+WWnmIuY2pobR6IOpB6xigc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -316,14 +314,8 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.aescorp.ru/dsp_dev/claim/common/sync_exchange v0.0.33 h1:sLKDG4x4Ov72Nd9LKhj2rZ55OrHLaUgxupqCF27808I=
gitlab.aescorp.ru/dsp_dev/claim/common/sync_exchange v0.0.33/go.mod h1:+ZbwpurumFpi0GbiahOY4shV6WB2kguFxN486+76prs=
gitlab.aescorp.ru/dsp_dev/claim/common/sync_exchange v0.0.34 h1:1w3epO1MYB3GAHYDEPNlJWtrSDt+eL+dCX1IqTI0HEk=
gitlab.aescorp.ru/dsp_dev/claim/common/sync_exchange v0.0.34/go.mod h1:+ZbwpurumFpi0GbiahOY4shV6WB2kguFxN486+76prs=
gitlab.aescorp.ru/dsp_dev/claim/common/sync_exchange v0.0.35 h1:Cgp9Q86ySYlx6l4YCrgR88gff9LKrF7TozSW/M9wQP8=
gitlab.aescorp.ru/dsp_dev/claim/common/sync_exchange v0.0.35/go.mod h1:+ZbwpurumFpi0GbiahOY4shV6WB2kguFxN486+76prs=
gitlab.aescorp.ru/dsp_dev/claim/sync_service v1.2.242 h1:bS4Cq65lLs0LYOV9hzXW0Gv2lIqXi1I1TBzwhln4iWw=
gitlab.aescorp.ru/dsp_dev/claim/sync_service v1.2.242/go.mod h1:VB93+iYc+6r5Jka9gRgQksjjW2MHtDltXAsHVP6ZELc=
gitlab.aescorp.ru/dsp_dev/claim/sync_service v1.2.268 h1:lPdwJ8heNvDu+5prMK2cqj+qd4IO43r6bc2kth+Y8Mo=
gitlab.aescorp.ru/dsp_dev/claim/sync_service v1.2.268/go.mod h1:mleo1C4JFt0qkNRkDhfQsTYR+xUXpSXJXe3VopssGCU=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
@@ -367,8 +359,6 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -405,8 +395,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -431,8 +419,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -459,8 +445,6 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -473,8 +457,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 h1:5bKytslY8ViY0Cj/ewmRtrWHW64bNF03cAatUUFCdFI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=

373
postgres_pgx/timestamptz.go Normal file
View File

@@ -0,0 +1,373 @@
package postgres_pgx
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
"strings"
"time"
)
const pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07"
const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00"
const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00"
const microsecFromUnixEpochToY2K = 946684800 * 1000000
const (
negativeInfinityMicrosecondOffset = -9223372036854775808
infinityMicrosecondOffset = 9223372036854775807
)
type TimestamptzScanner interface {
ScanTimestamptz(v Timestamptz) error
}
type TimestamptzValuer interface {
TimestamptzValue() (Timestamptz, error)
}
// Timestamptz represents the PostgreSQL timestamptz type.
type Timestamptz struct {
Time time.Time
InfinityModifier pgtype.InfinityModifier
Valid bool
}
func (tstz *Timestamptz) ScanTimestamptz(v Timestamptz) error {
*tstz = v
return nil
}
func (tstz Timestamptz) TimestamptzValue() (Timestamptz, error) {
return tstz, nil
}
// Scan implements the database/sql Scanner interface.
func (tstz *Timestamptz) Scan(src any) error {
if src == nil {
*tstz = Timestamptz{}
return nil
}
switch src := src.(type) {
case string:
return (&scanPlanTextTimestamptzToTimestamptzScanner{}).Scan([]byte(src), tstz)
case time.Time:
*tstz = Timestamptz{Time: src, Valid: true}
return nil
}
return fmt.Errorf("cannot scan %T", src)
}
// Value implements the database/sql/driver Valuer interface.
func (tstz Timestamptz) Value() (driver.Value, error) {
if !tstz.Valid {
return nil, nil
}
if tstz.InfinityModifier != pgtype.Finite {
return tstz.InfinityModifier.String(), nil
}
return tstz.Time, nil
}
func (tstz Timestamptz) MarshalJSON() ([]byte, error) {
if !tstz.Valid {
return []byte("null"), nil
}
var s string
switch tstz.InfinityModifier {
case pgtype.Finite:
s = tstz.Time.Format(time.RFC3339Nano)
case pgtype.Infinity:
s = "infinity"
case pgtype.NegativeInfinity:
s = "-infinity"
}
return json.Marshal(s)
}
func (tstz *Timestamptz) UnmarshalJSON(b []byte) error {
var s *string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
if s == nil {
*tstz = Timestamptz{}
return nil
}
switch *s {
case "infinity":
*tstz = Timestamptz{Valid: true, InfinityModifier: pgtype.Infinity}
case "-infinity":
*tstz = Timestamptz{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
// PostgreSQL uses ISO 8601 for to_json function and casting from a string to timestamptz
tim, err := time.Parse(time.RFC3339Nano, *s)
if err != nil {
return err
}
*tstz = Timestamptz{Time: tim, Valid: true}
}
return nil
}
type TimestamptzCodec struct {
// ScanLocation is the location to return scanned timestamptz values in. This does not change the instant in time that
// the timestamptz represents.
ScanLocation *time.Location
}
func (*TimestamptzCodec) FormatSupported(format int16) bool {
return format == pgtype.TextFormatCode || format == pgtype.BinaryFormatCode
}
func (*TimestamptzCodec) PreferredFormat() int16 {
return pgtype.BinaryFormatCode
}
func (*TimestamptzCodec) PlanEncode(m *pgtype.Map, oid uint32, format int16, value any) pgtype.EncodePlan {
if _, ok := value.(TimestamptzValuer); !ok {
return nil
}
switch format {
case pgtype.BinaryFormatCode:
return encodePlanTimestamptzCodecBinary{}
case pgtype.TextFormatCode:
return encodePlanTimestamptzCodecText{}
}
return nil
}
type encodePlanTimestamptzCodecBinary struct{}
func (encodePlanTimestamptzCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
ts, err := value.(TimestamptzValuer).TimestamptzValue()
if err != nil {
return nil, err
}
if !ts.Valid {
return nil, nil
}
var microsecSinceY2K int64
switch ts.InfinityModifier {
case pgtype.Finite:
microsecSinceUnixEpoch := ts.Time.Unix()*1000000 + int64(ts.Time.Nanosecond())/1000
microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
case pgtype.Infinity:
microsecSinceY2K = infinityMicrosecondOffset
case pgtype.NegativeInfinity:
microsecSinceY2K = negativeInfinityMicrosecondOffset
}
buf = AppendInt64(buf, microsecSinceY2K)
return buf, nil
}
type encodePlanTimestamptzCodecText struct{}
func (encodePlanTimestamptzCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
ts, err := value.(TimestamptzValuer).TimestamptzValue()
if err != nil {
return nil, err
}
if !ts.Valid {
return nil, nil
}
var s string
switch ts.InfinityModifier {
case pgtype.Finite:
t := ts.Time.UTC().Truncate(time.Microsecond)
// Year 0000 is 1 BC
bc := false
if year := t.Year(); year <= 0 {
year = -year + 1
t = time.Date(year, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
bc = true
}
s = t.Format(pgTimestamptzSecondFormat)
if bc {
s = s + " BC"
}
case pgtype.Infinity:
s = "infinity"
case pgtype.NegativeInfinity:
s = "-infinity"
}
buf = append(buf, s...)
return buf, nil
}
func (c *TimestamptzCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, target any) pgtype.ScanPlan {
switch format {
case pgtype.BinaryFormatCode:
switch target.(type) {
case TimestamptzScanner:
return &scanPlanBinaryTimestamptzToTimestamptzScanner{location: c.ScanLocation}
}
case pgtype.TextFormatCode:
switch target.(type) {
case TimestamptzScanner:
return &scanPlanTextTimestamptzToTimestamptzScanner{location: c.ScanLocation}
}
}
return nil
}
type scanPlanBinaryTimestamptzToTimestamptzScanner struct{ location *time.Location }
func (plan *scanPlanBinaryTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {
scanner := (dst).(TimestamptzScanner)
if src == nil {
return scanner.ScanTimestamptz(Timestamptz{})
}
if len(src) != 8 {
return fmt.Errorf("invalid length for timestamptz: %v", len(src))
}
var tstz Timestamptz
microsecSinceY2K := int64(binary.BigEndian.Uint64(src))
switch microsecSinceY2K {
case infinityMicrosecondOffset:
tstz = Timestamptz{Valid: true, InfinityModifier: pgtype.Infinity}
case negativeInfinityMicrosecondOffset:
tstz = Timestamptz{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
tim := time.Unix(
microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000,
(microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000),
)
if plan.location != nil {
tim = tim.In(plan.location)
}
tstz = Timestamptz{Time: tim, Valid: true}
}
return scanner.ScanTimestamptz(tstz)
}
type scanPlanTextTimestamptzToTimestamptzScanner struct{ location *time.Location }
func (plan *scanPlanTextTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {
scanner := (dst).(TimestamptzScanner)
if src == nil {
return scanner.ScanTimestamptz(Timestamptz{})
}
var tstz Timestamptz
sbuf := string(src)
switch sbuf {
case "infinity":
tstz = Timestamptz{Valid: true, InfinityModifier: pgtype.Infinity}
case "-infinity":
tstz = Timestamptz{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
bc := false
if strings.HasSuffix(sbuf, " BC") {
sbuf = sbuf[:len(sbuf)-3]
bc = true
}
var format string
if len(sbuf) >= 9 && (sbuf[len(sbuf)-9] == '-' || sbuf[len(sbuf)-9] == '+') {
format = pgTimestamptzSecondFormat
} else if len(sbuf) >= 6 && (sbuf[len(sbuf)-6] == '-' || sbuf[len(sbuf)-6] == '+') {
format = pgTimestamptzMinuteFormat
} else {
format = pgTimestamptzHourFormat
}
tim, err := time.Parse(format, sbuf)
if err != nil {
return err
}
if bc {
year := -tim.Year() + 1
tim = time.Date(year, tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), tim.Location())
}
if plan.location != nil {
tim = tim.In(plan.location)
}
tstz = Timestamptz{Time: tim, Valid: true}
}
return scanner.ScanTimestamptz(tstz)
}
func (c *TimestamptzCodec) DecodeDatabaseSQLValue(m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
if src == nil {
return nil, nil
}
var tstz Timestamptz
err := codecScan(c, m, oid, format, src, &tstz)
if err != nil {
return nil, err
}
if tstz.InfinityModifier != pgtype.Finite {
return tstz.InfinityModifier.String(), nil
}
return tstz.Time, nil
}
func (c *TimestamptzCodec) DecodeValue(m *pgtype.Map, oid uint32, format int16, src []byte) (any, error) {
if src == nil {
return nil, nil
}
var tstz Timestamptz
err := codecScan(c, m, oid, format, src, &tstz)
if err != nil {
return nil, err
}
if tstz.InfinityModifier != pgtype.Finite {
return tstz.InfinityModifier, nil
}
return tstz.Time, nil
}
func codecScan(codec pgtype.Codec, m *pgtype.Map, oid uint32, format int16, src []byte, dst any) error {
scanPlan := codec.PlanScan(m, oid, format, dst)
if scanPlan == nil {
return fmt.Errorf("PlanScan did not find a plan")
}
return scanPlan.Scan(src, dst)
}

40
postgres_pgx/write.go Normal file
View File

@@ -0,0 +1,40 @@
package postgres_pgx
import "encoding/binary"
func AppendUint16(buf []byte, n uint16) []byte {
wp := len(buf)
buf = append(buf, 0, 0)
binary.BigEndian.PutUint16(buf[wp:], n)
return buf
}
func AppendUint32(buf []byte, n uint32) []byte {
wp := len(buf)
buf = append(buf, 0, 0, 0, 0)
binary.BigEndian.PutUint32(buf[wp:], n)
return buf
}
func AppendUint64(buf []byte, n uint64) []byte {
wp := len(buf)
buf = append(buf, 0, 0, 0, 0, 0, 0, 0, 0)
binary.BigEndian.PutUint64(buf[wp:], n)
return buf
}
func AppendInt16(buf []byte, n int16) []byte {
return AppendUint16(buf, uint16(n))
}
func AppendInt32(buf []byte, n int32) []byte {
return AppendUint32(buf, uint32(n))
}
func AppendInt64(buf []byte, n int64) []byte {
return AppendUint64(buf, uint64(n))
}
func SetInt32(buf []byte, n int32) {
binary.BigEndian.PutUint32(buf, uint32(n))
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/ManyakRus/starter/port_checker"
"github.com/ManyakRus/starter/postgres_pgx"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"strings"
"time"
@@ -45,6 +46,7 @@ type SettingsINI struct {
DB_SCHEMA string
DB_USER string
DB_PASSWORD string
NoNUll bool
}
// TextConnBusy - текст ошибки "conn busy"
@@ -53,7 +55,7 @@ const TextConnBusy = "conn busy"
// timeOutSeconds - время ожидания для Ping()
const timeOutSeconds = 1
// Connect_err - подключается к базе данных
// Connect - подключается к базе данных
func Connect() {
if Settings.DB_HOST == "" {
@@ -67,6 +69,30 @@ func Connect() {
}
// Connect_err - подключается к базе данных, возвращает ошибку
func Connect_err() error {
var err error
err = Connect_WithApplicationName_err("")
return err
}
// Connect_NoNull - подключается к базе данных
// запросы вместо null возвращают значение по умолчанию (пока только дата)
func Connect_NoNull() {
if Settings.DB_HOST == "" {
FillSettings()
}
Settings.NoNUll = true
port_checker.CheckPort(Settings.DB_HOST, Settings.DB_PORT)
err := Connect_err()
LogInfo_Connected(err)
}
// LogInfo_Connected - выводит сообщение в Лог, или паника при ошибке
func LogInfo_Connected(err error) {
if err != nil {
@@ -77,14 +103,6 @@ func LogInfo_Connected(err error) {
}
// Connect_err - подключается к базе данных
func Connect_err() error {
var err error
err = Connect_WithApplicationName_err("")
return err
}
// Connect_WithApplicationName - подключается к базе данных, с указанием имени приложения
func Connect_WithApplicationName(ApplicationName string) {
err := Connect_WithApplicationName_err(ApplicationName)
@@ -113,6 +131,9 @@ func Connect_WithApplicationName_err(ApplicationName string) error {
//
config, err := pgxpool.ParseConfig(databaseUrl)
if Settings.NoNUll == true {
config.AfterConnect = AfterConnect_NoNull
}
//config.PreferSimpleProtocol = true //для мульти-запросов
PgxPool = nil
PgxPool, err = pgxpool.NewWithConfig(ctx, config)
@@ -311,6 +332,22 @@ func Start_ctx(ctx *context.Context, WaitGroup *sync.WaitGroup) error {
return err
}
// Start_NoNull - делает соединение с БД, отключение и др.
// запросы вместо null возвращают значение по умолчанию (пока только дата)
func Start_NoNull(ApplicationName string) {
Settings.NoNUll = true
err := Connect_WithApplicationName_err(ApplicationName)
LogInfo_Connected(err)
stopapp.GetWaitGroup_Main().Add(1)
go WaitStop()
stopapp.GetWaitGroup_Main().Add(1)
go ping_go()
}
// Start - делает соединение с БД, отключение и др.
func Start(ApplicationName string) {
err := Connect_WithApplicationName_err(ApplicationName)
@@ -559,3 +596,14 @@ func ReplaceSchemaName(TextSQL, SchemaNameFrom string) string {
return Otvet
}
func AfterConnect_NoNull(ctx context.Context, conn *pgx.Conn) error {
// Регистрируем zeronull обработчики для нужных типов
conn.TypeMap().RegisterType(&pgtype.Type{
Name: "timestamptz",
OID: pgtype.TimestamptzOID,
Codec: &postgres_pgx.TimestamptzCodec{},
})
return nil
}

22
vendor/github.com/jackc/pgx/v5/pgtype/zeronull/doc.go generated vendored Normal file
View File

@@ -0,0 +1,22 @@
// Package zeronull contains types that automatically convert between database NULLs and Go zero values.
/*
Sometimes the distinction between a zero value and a NULL value is not useful at the application level. For example,
in PostgreSQL an empty string may be stored as NULL. There is usually no application level distinction between an
empty string and a NULL string. Package zeronull implements types that seamlessly convert between PostgreSQL NULL and
the zero value.
It is recommended to convert types at usage time rather than instantiate these types directly. In the example below,
middlename would be stored as a NULL.
firstname := "John"
middlename := ""
lastname := "Smith"
_, err := conn.Exec(
ctx,
"insert into people(firstname, middlename, lastname) values($1, $2, $3)",
zeronull.Text(firstname),
zeronull.Text(middlename),
zeronull.Text(lastname),
)
*/
package zeronull

View File

@@ -0,0 +1,56 @@
package zeronull
import (
"database/sql/driver"
"github.com/jackc/pgx/v5/pgtype"
)
type Float8 float64
func (Float8) SkipUnderlyingTypePlan() {}
// ScanFloat64 implements the Float64Scanner interface.
func (f *Float8) ScanFloat64(n pgtype.Float8) error {
if !n.Valid {
*f = 0
return nil
}
*f = Float8(n.Float64)
return nil
}
func (f Float8) Float64Value() (pgtype.Float8, error) {
if f == 0 {
return pgtype.Float8{}, nil
}
return pgtype.Float8{Float64: float64(f), Valid: true}, nil
}
// Scan implements the database/sql Scanner interface.
func (f *Float8) Scan(src any) error {
if src == nil {
*f = 0
return nil
}
var nullable pgtype.Float8
err := nullable.Scan(src)
if err != nil {
return err
}
*f = Float8(nullable.Float64)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (f Float8) Value() (driver.Value, error) {
if f == 0 {
return nil, nil
}
return float64(f), nil
}

154
vendor/github.com/jackc/pgx/v5/pgtype/zeronull/int.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
// Do not edit. Generated from pgtype/zeronull/int.go.erb
package zeronull
import (
"database/sql/driver"
"fmt"
"math"
"github.com/jackc/pgx/v5/pgtype"
)
type Int2 int16
func (Int2) SkipUnderlyingTypePlan() {}
// ScanInt64 implements the Int64Scanner interface.
func (dst *Int2) ScanInt64(n int64, valid bool) error {
if !valid {
*dst = 0
return nil
}
if n < math.MinInt16 {
return fmt.Errorf("%d is greater than maximum value for Int2", n)
}
if n > math.MaxInt16 {
return fmt.Errorf("%d is greater than maximum value for Int2", n)
}
*dst = Int2(n)
return nil
}
// Scan implements the database/sql Scanner interface.
func (dst *Int2) Scan(src any) error {
if src == nil {
*dst = 0
return nil
}
var nullable pgtype.Int2
err := nullable.Scan(src)
if err != nil {
return err
}
*dst = Int2(nullable.Int16)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (src Int2) Value() (driver.Value, error) {
if src == 0 {
return nil, nil
}
return int64(src), nil
}
type Int4 int32
func (Int4) SkipUnderlyingTypePlan() {}
// ScanInt64 implements the Int64Scanner interface.
func (dst *Int4) ScanInt64(n int64, valid bool) error {
if !valid {
*dst = 0
return nil
}
if n < math.MinInt32 {
return fmt.Errorf("%d is greater than maximum value for Int4", n)
}
if n > math.MaxInt32 {
return fmt.Errorf("%d is greater than maximum value for Int4", n)
}
*dst = Int4(n)
return nil
}
// Scan implements the database/sql Scanner interface.
func (dst *Int4) Scan(src any) error {
if src == nil {
*dst = 0
return nil
}
var nullable pgtype.Int4
err := nullable.Scan(src)
if err != nil {
return err
}
*dst = Int4(nullable.Int32)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (src Int4) Value() (driver.Value, error) {
if src == 0 {
return nil, nil
}
return int64(src), nil
}
type Int8 int64
func (Int8) SkipUnderlyingTypePlan() {}
// ScanInt64 implements the Int64Scanner interface.
func (dst *Int8) ScanInt64(n int64, valid bool) error {
if !valid {
*dst = 0
return nil
}
if n < math.MinInt64 {
return fmt.Errorf("%d is greater than maximum value for Int8", n)
}
if n > math.MaxInt64 {
return fmt.Errorf("%d is greater than maximum value for Int8", n)
}
*dst = Int8(n)
return nil
}
// Scan implements the database/sql Scanner interface.
func (dst *Int8) Scan(src any) error {
if src == nil {
*dst = 0
return nil
}
var nullable pgtype.Int8
err := nullable.Scan(src)
if err != nil {
return err
}
*dst = Int8(nullable.Int64)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (src Int8) Value() (driver.Value, error) {
if src == 0 {
return nil, nil
}
return int64(src), nil
}

View File

@@ -0,0 +1,60 @@
package zeronull
import (
"database/sql/driver"
"fmt"
"math"
"github.com/jackc/pgx/v5/pgtype"
)
<% [2, 4, 8].each do |pg_byte_size| %>
<% pg_bit_size = pg_byte_size * 8 %>
type Int<%= pg_byte_size %> int<%= pg_bit_size %>
func (Int<%= pg_byte_size %>) SkipUnderlyingTypePlan() {}
// ScanInt64 implements the Int64Scanner interface.
func (dst *Int<%= pg_byte_size %>) ScanInt64(n int64, valid bool) error {
if !valid {
*dst = 0
return nil
}
if n < math.MinInt<%= pg_bit_size %> {
return fmt.Errorf("%d is greater than maximum value for Int<%= pg_byte_size %>", n)
}
if n > math.MaxInt<%= pg_bit_size %> {
return fmt.Errorf("%d is greater than maximum value for Int<%= pg_byte_size %>", n)
}
*dst = Int<%= pg_byte_size %>(n)
return nil
}
// Scan implements the database/sql Scanner interface.
func (dst *Int<%= pg_byte_size %>) Scan(src any) error {
if src == nil {
*dst = 0
return nil
}
var nullable pgtype.Int<%= pg_byte_size %>
err := nullable.Scan(src)
if err != nil {
return err
}
*dst = Int<%= pg_byte_size %>(nullable.Int<%= pg_bit_size %>)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (src Int<%= pg_byte_size %>) Value() (driver.Value, error) {
if src == 0 {
return nil, nil
}
return int64(src), nil
}
<% end %>

View File

@@ -0,0 +1,31 @@
package zeronull_test
import (
"testing"
"github.com/jackc/pgx/v5/pgtype/testutil"
"github.com/jackc/pgx/v5/pgtype/zeronull"
)
<% [2, 4, 8].each do |pg_byte_size| %>
<% pg_bit_size = pg_byte_size * 8 %>
func TestInt<%= pg_byte_size %>Transcode(t *testing.T) {
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int<%= pg_byte_size %>", []pgxtest.ValueRoundTripTest{
{
(zeronull.Int<%= pg_byte_size %>)(1),
new(zeronull.Int<%= pg_byte_size %>),
isExpectedEq((zeronull.Int<%= pg_byte_size %>)(1)),
},
{
nil,
new(zeronull.Int<%= pg_byte_size %>),
isExpectedEq((zeronull.Int<%= pg_byte_size %>)(0)),
},
{
(zeronull.Int<%= pg_byte_size %>)(0),
new(any),
isExpectedEq(nil),
},
})
}
<% end %>

49
vendor/github.com/jackc/pgx/v5/pgtype/zeronull/text.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package zeronull
import (
"database/sql/driver"
"github.com/jackc/pgx/v5/pgtype"
)
type Text string
func (Text) SkipUnderlyingTypePlan() {}
// ScanText implements the TextScanner interface.
func (dst *Text) ScanText(v pgtype.Text) error {
if !v.Valid {
*dst = ""
return nil
}
*dst = Text(v.String)
return nil
}
// Scan implements the database/sql Scanner interface.
func (dst *Text) Scan(src any) error {
if src == nil {
*dst = ""
return nil
}
var nullable pgtype.Text
err := nullable.Scan(src)
if err != nil {
return err
}
*dst = Text(nullable.String)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (src Text) Value() (driver.Value, error) {
if src == "" {
return nil, nil
}
return string(src), nil
}

View File

@@ -0,0 +1,67 @@
package zeronull
import (
"database/sql/driver"
"fmt"
"time"
"github.com/jackc/pgx/v5/pgtype"
)
type Timestamp time.Time
func (Timestamp) SkipUnderlyingTypePlan() {}
func (ts *Timestamp) ScanTimestamp(v pgtype.Timestamp) error {
if !v.Valid {
*ts = Timestamp{}
return nil
}
switch v.InfinityModifier {
case pgtype.Finite:
*ts = Timestamp(v.Time)
return nil
case pgtype.Infinity:
return fmt.Errorf("cannot scan Infinity into *time.Time")
case pgtype.NegativeInfinity:
return fmt.Errorf("cannot scan -Infinity into *time.Time")
default:
return fmt.Errorf("invalid InfinityModifier: %v", v.InfinityModifier)
}
}
func (ts Timestamp) TimestampValue() (pgtype.Timestamp, error) {
if time.Time(ts).IsZero() {
return pgtype.Timestamp{}, nil
}
return pgtype.Timestamp{Time: time.Time(ts), Valid: true}, nil
}
// Scan implements the database/sql Scanner interface.
func (ts *Timestamp) Scan(src any) error {
if src == nil {
*ts = Timestamp{}
return nil
}
var nullable pgtype.Timestamp
err := nullable.Scan(src)
if err != nil {
return err
}
*ts = Timestamp(nullable.Time)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (ts Timestamp) Value() (driver.Value, error) {
if time.Time(ts).IsZero() {
return nil, nil
}
return time.Time(ts), nil
}

View File

@@ -0,0 +1,67 @@
package zeronull
import (
"database/sql/driver"
"fmt"
"time"
"github.com/jackc/pgx/v5/pgtype"
)
type Timestamptz time.Time
func (Timestamptz) SkipUnderlyingTypePlan() {}
func (ts *Timestamptz) ScanTimestamptz(v pgtype.Timestamptz) error {
if !v.Valid {
*ts = Timestamptz{}
return nil
}
switch v.InfinityModifier {
case pgtype.Finite:
*ts = Timestamptz(v.Time)
return nil
case pgtype.Infinity:
return fmt.Errorf("cannot scan Infinity into *time.Time")
case pgtype.NegativeInfinity:
return fmt.Errorf("cannot scan -Infinity into *time.Time")
default:
return fmt.Errorf("invalid InfinityModifier: %v", v.InfinityModifier)
}
}
func (ts Timestamptz) TimestamptzValue() (pgtype.Timestamptz, error) {
if time.Time(ts).IsZero() {
return pgtype.Timestamptz{}, nil
}
return pgtype.Timestamptz{Time: time.Time(ts), Valid: true}, nil
}
// Scan implements the database/sql Scanner interface.
func (ts *Timestamptz) Scan(src any) error {
if src == nil {
*ts = Timestamptz{}
return nil
}
var nullable pgtype.Timestamptz
err := nullable.Scan(src)
if err != nil {
return err
}
*ts = Timestamptz(nullable.Time)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (ts Timestamptz) Value() (driver.Value, error) {
if time.Time(ts).IsZero() {
return nil, nil
}
return time.Time(ts), nil
}

62
vendor/github.com/jackc/pgx/v5/pgtype/zeronull/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,62 @@
package zeronull
import (
"database/sql/driver"
"github.com/jackc/pgx/v5/pgtype"
)
type UUID [16]byte
func (UUID) SkipUnderlyingTypePlan() {}
// ScanUUID implements the UUIDScanner interface.
func (u *UUID) ScanUUID(v pgtype.UUID) error {
if !v.Valid {
*u = UUID{}
return nil
}
*u = UUID(v.Bytes)
return nil
}
func (u UUID) UUIDValue() (pgtype.UUID, error) {
if u == (UUID{}) {
return pgtype.UUID{}, nil
}
return pgtype.UUID{Bytes: u, Valid: true}, nil
}
// Scan implements the database/sql Scanner interface.
func (u *UUID) Scan(src any) error {
if src == nil {
*u = UUID{}
return nil
}
var nullable pgtype.UUID
err := nullable.Scan(src)
if err != nil {
return err
}
*u = UUID(nullable.Bytes)
return nil
}
// Value implements the database/sql/driver Valuer interface.
func (u UUID) Value() (driver.Value, error) {
if u == (UUID{}) {
return nil, nil
}
buf, err := pgtype.UUIDCodec{}.PlanEncode(nil, pgtype.UUIDOID, pgtype.TextFormatCode, u).Encode(u, nil)
if err != nil {
return nil, err
}
return string(buf), nil
}

View File

@@ -0,0 +1,17 @@
package zeronull
import (
"github.com/jackc/pgx/v5/pgtype"
)
// Register registers the zeronull types so they can be used in query exec modes that do not know the server OIDs.
func Register(m *pgtype.Map) {
m.RegisterDefaultPgType(Float8(0), "float8")
m.RegisterDefaultPgType(Int2(0), "int2")
m.RegisterDefaultPgType(Int4(0), "int4")
m.RegisterDefaultPgType(Int8(0), "int8")
m.RegisterDefaultPgType(Text(""), "text")
m.RegisterDefaultPgType(Timestamp{}, "timestamp")
m.RegisterDefaultPgType(Timestamptz{}, "timestamptz")
m.RegisterDefaultPgType(UUID{}, "uuid")
}

1
vendor/modules.txt vendored
View File

@@ -312,6 +312,7 @@ github.com/jackc/pgx/v5/pgconn/ctxwatch
github.com/jackc/pgx/v5/pgconn/internal/bgreader
github.com/jackc/pgx/v5/pgproto3
github.com/jackc/pgx/v5/pgtype
github.com/jackc/pgx/v5/pgtype/zeronull
github.com/jackc/pgx/v5/pgxpool
github.com/jackc/pgx/v5/stdlib
# github.com/jackc/puddle/v2 v2.2.2