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

сделал postgres_pgtype

This commit is contained in:
Nikitin Aleksandr
2025-07-02 17:23:34 +03:00
parent f3337f07b4
commit 0e197b8401
10 changed files with 1489 additions and 9 deletions

356
postgres_pgtype/date.go Normal file
View File

@@ -0,0 +1,356 @@
// копия файла из https://github.com/jackc/pgtype/timestamptz.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgtype
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
"regexp"
"strconv"
"time"
)
//type DateScanner interface {
// ScanDate(v Date) error
//}
//
//type DateValuer interface {
// DateValue() (Date, error)
//}
type Date struct {
Time time.Time
InfinityModifier pgtype.InfinityModifier
Valid bool
}
func (d *Date) ScanDate(v Date) error {
*d = v
return nil
}
func (d Date) DateValue() (Date, error) {
return d, nil
}
const (
negativeInfinityDayOffset = -2147483648
infinityDayOffset = 2147483647
)
// Scan implements the database/sql Scanner interface.
func (dst *Date) Scan(src any) error {
if src == nil {
*dst = Date{Valid: true} //sanek
//*dst = Date{}
return nil
}
switch src := src.(type) {
case string:
return scanPlanTextAnyToDateScanner{}.Scan([]byte(src), dst)
case time.Time:
*dst = Date{Time: src, Valid: true}
return nil
}
return fmt.Errorf("cannot scan %T", src)
}
// Value implements the database/sql/driver Valuer interface.
func (src Date) Value() (driver.Value, error) {
if !src.Valid {
return nil, nil
}
if src.InfinityModifier != pgtype.Finite {
return src.InfinityModifier.String(), nil
}
return src.Time, nil
}
func (src Date) MarshalJSON() ([]byte, error) {
if !src.Valid {
return []byte("null"), nil
}
var s string
switch src.InfinityModifier {
case pgtype.Finite:
s = src.Time.Format("2006-01-02")
case pgtype.Infinity:
s = "infinity"
case pgtype.NegativeInfinity:
s = "-infinity"
}
return json.Marshal(s)
}
func (dst *Date) UnmarshalJSON(b []byte) error {
var s *string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
if s == nil {
*dst = Date{}
return nil
}
switch *s {
case "infinity":
*dst = Date{Valid: true, InfinityModifier: pgtype.Infinity}
case "-infinity":
*dst = Date{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
t, err := time.ParseInLocation("2006-01-02", *s, time.UTC)
if err != nil {
return err
}
*dst = Date{Time: t, Valid: true}
}
return nil
}
type DateCodec struct{}
func (DateCodec) FormatSupported(format int16) bool {
return format == pgtype.TextFormatCode || format == pgtype.BinaryFormatCode
}
func (DateCodec) PreferredFormat() int16 {
return pgtype.BinaryFormatCode
}
func (DateCodec) PlanEncode(m *pgtype.Map, oid uint32, format int16, value any) pgtype.EncodePlan {
if _, ok := value.(pgtype.DateValuer); !ok {
return nil
}
switch format {
case pgtype.BinaryFormatCode:
return encodePlanDateCodecBinary{}
case pgtype.TextFormatCode:
return encodePlanDateCodecText{}
}
return nil
}
type encodePlanDateCodecBinary struct{}
func (encodePlanDateCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
date, err := value.(pgtype.DateValuer).DateValue()
if err != nil {
return nil, err
}
if !date.Valid {
return nil, nil
}
var daysSinceDateEpoch int32
switch date.InfinityModifier {
case pgtype.Finite:
tUnix := time.Date(date.Time.Year(), date.Time.Month(), date.Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
dateEpoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
secSinceDateEpoch := tUnix - dateEpoch
daysSinceDateEpoch = int32(secSinceDateEpoch / 86400)
case pgtype.Infinity:
daysSinceDateEpoch = infinityDayOffset
case pgtype.NegativeInfinity:
daysSinceDateEpoch = negativeInfinityDayOffset
}
return AppendInt32(buf, daysSinceDateEpoch), nil
}
type encodePlanDateCodecText struct{}
func (encodePlanDateCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
date, err := value.(pgtype.DateValuer).DateValue()
if err != nil {
return nil, err
}
if !date.Valid {
return nil, nil
}
switch date.InfinityModifier {
case pgtype.Finite:
// Year 0000 is 1 BC
bc := false
year := date.Time.Year()
if year <= 0 {
year = -year + 1
bc = true
}
yearBytes := strconv.AppendInt(make([]byte, 0, 6), int64(year), 10)
for i := len(yearBytes); i < 4; i++ {
buf = append(buf, '0')
}
buf = append(buf, yearBytes...)
buf = append(buf, '-')
if date.Time.Month() < 10 {
buf = append(buf, '0')
}
buf = strconv.AppendInt(buf, int64(date.Time.Month()), 10)
buf = append(buf, '-')
if date.Time.Day() < 10 {
buf = append(buf, '0')
}
buf = strconv.AppendInt(buf, int64(date.Time.Day()), 10)
if bc {
buf = append(buf, " BC"...)
}
case pgtype.Infinity:
buf = append(buf, "infinity"...)
case pgtype.NegativeInfinity:
buf = append(buf, "-infinity"...)
}
return buf, nil
}
func (DateCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, target any) pgtype.ScanPlan {
switch format {
case pgtype.BinaryFormatCode:
name := getInterfaceName(target) //sanek
switch name {
case "*pgtype.timeWrapper":
return scanPlanBinaryDateToDateScanner{}
}
case pgtype.TextFormatCode:
name := getInterfaceName(target) //sanek
switch name {
case "*pgtype.timeWrapper":
return scanPlanTextAnyToDateScanner{}
}
}
return nil
}
type scanPlanBinaryDateToDateScanner struct{}
func (scanPlanBinaryDateToDateScanner) Scan(src []byte, dst any) error {
scanner := (dst).(pgtype.DateScanner)
if src == nil {
return scanner.ScanDate(pgtype.Date{})
}
if len(src) != 4 {
return fmt.Errorf("invalid length for date: %v", len(src))
}
dayOffset := int32(binary.BigEndian.Uint32(src))
switch dayOffset {
case infinityDayOffset:
return scanner.ScanDate(pgtype.Date{InfinityModifier: pgtype.Infinity, Valid: true})
case negativeInfinityDayOffset:
return scanner.ScanDate(pgtype.Date{InfinityModifier: -pgtype.Infinity, Valid: true})
default:
t := time.Date(2000, 1, int(1+dayOffset), 0, 0, 0, 0, time.UTC)
return scanner.ScanDate(pgtype.Date{Time: t, Valid: true})
}
}
type scanPlanTextAnyToDateScanner struct{}
var dateRegexp = regexp.MustCompile(`^(\d{4,})-(\d\d)-(\d\d)( BC)?$`)
func (scanPlanTextAnyToDateScanner) Scan(src []byte, dst any) error {
scanner := (dst).(pgtype.DateScanner)
if src == nil {
return scanner.ScanDate(pgtype.Date{})
}
sbuf := string(src)
match := dateRegexp.FindStringSubmatch(sbuf)
if match != nil {
year, err := strconv.ParseInt(match[1], 10, 32)
if err != nil {
return fmt.Errorf("BUG: cannot parse date that regexp matched (year): %w", err)
}
month, err := strconv.ParseInt(match[2], 10, 32)
if err != nil {
return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %w", err)
}
day, err := strconv.ParseInt(match[3], 10, 32)
if err != nil {
return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %w", err)
}
// BC matched
if len(match[4]) > 0 {
year = -year + 1
}
t := time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, time.UTC)
return scanner.ScanDate(pgtype.Date{Time: t, Valid: true})
}
switch sbuf {
case "infinity":
return scanner.ScanDate(pgtype.Date{InfinityModifier: pgtype.Infinity, Valid: true})
case "-infinity":
return scanner.ScanDate(pgtype.Date{InfinityModifier: -pgtype.Infinity, Valid: true})
default:
return fmt.Errorf("invalid date format")
}
}
func (c DateCodec) DecodeDatabaseSQLValue(m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
if src == nil {
return nil, nil
}
var date Date
err := codecScan(c, m, oid, format, src, &date)
if err != nil {
return nil, err
}
if date.InfinityModifier != pgtype.Finite {
return date.InfinityModifier.String(), nil
}
return date.Time, nil
}
func (c DateCodec) DecodeValue(m *pgtype.Map, oid uint32, format int16, src []byte) (any, error) {
if src == nil {
return nil, nil
}
var date Date
err := codecScan(c, m, oid, format, src, &date)
if err != nil {
return nil, err
}
if date.InfinityModifier != pgtype.Finite {
return date.InfinityModifier, nil
}
return date.Time, nil
}

299
postgres_pgtype/interval.go Normal file
View File

@@ -0,0 +1,299 @@
// копия файла из https://github.com/jackc/pgtype/interval.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgtype
import (
"database/sql/driver"
"encoding/binary"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
"strconv"
"strings"
)
const (
microsecondsPerSecond = 1000000
microsecondsPerMinute = 60 * microsecondsPerSecond
microsecondsPerHour = 60 * microsecondsPerMinute
microsecondsPerDay = 24 * microsecondsPerHour
microsecondsPerMonth = 30 * microsecondsPerDay
)
type IntervalScanner interface {
ScanInterval(v Interval) error
}
type IntervalValuer interface {
IntervalValue() (Interval, error)
}
type Interval struct {
Microseconds int64
Days int32
Months int32
Valid bool
}
func (interval *Interval) ScanInterval(v Interval) error {
*interval = v
return nil
}
func (interval Interval) IntervalValue() (Interval, error) {
return interval, nil
}
// Scan implements the database/sql Scanner interface.
func (interval *Interval) Scan(src any) error {
if src == nil {
*interval = Interval{}
return nil
}
switch src := src.(type) {
case string:
return scanPlanTextAnyToIntervalScanner{}.Scan([]byte(src), interval)
}
return fmt.Errorf("cannot scan %T", src)
}
// Value implements the database/sql/driver Valuer interface.
func (interval Interval) Value() (driver.Value, error) {
if !interval.Valid {
return nil, nil
}
buf, err := pgtype.IntervalCodec{}.PlanEncode(nil, 0, pgtype.TextFormatCode, interval).Encode(interval, nil)
if err != nil {
return nil, err
}
return string(buf), err
}
type IntervalCodec struct{}
func (IntervalCodec) FormatSupported(format int16) bool {
return format == pgtype.TextFormatCode || format == pgtype.BinaryFormatCode
}
func (IntervalCodec) PreferredFormat() int16 {
return pgtype.BinaryFormatCode
}
func (IntervalCodec) PlanEncode(m *pgtype.Map, oid uint32, format int16, value any) pgtype.EncodePlan {
if _, ok := value.(IntervalValuer); !ok {
return nil
}
switch format {
case pgtype.BinaryFormatCode:
return encodePlanIntervalCodecBinary{}
case pgtype.TextFormatCode:
return encodePlanIntervalCodecText{}
}
return nil
}
type encodePlanIntervalCodecBinary struct{}
func (encodePlanIntervalCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
interval, err := value.(IntervalValuer).IntervalValue()
if err != nil {
return nil, err
}
if !interval.Valid {
return nil, nil
}
buf = AppendInt64(buf, interval.Microseconds)
buf = AppendInt32(buf, interval.Days)
buf = AppendInt32(buf, interval.Months)
return buf, nil
}
type encodePlanIntervalCodecText struct{}
func (encodePlanIntervalCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
interval, err := value.(IntervalValuer).IntervalValue()
if err != nil {
return nil, err
}
if !interval.Valid {
return nil, nil
}
if interval.Months != 0 {
buf = append(buf, strconv.FormatInt(int64(interval.Months), 10)...)
buf = append(buf, " mon "...)
}
if interval.Days != 0 {
buf = append(buf, strconv.FormatInt(int64(interval.Days), 10)...)
buf = append(buf, " day "...)
}
absMicroseconds := interval.Microseconds
if absMicroseconds < 0 {
absMicroseconds = -absMicroseconds
buf = append(buf, '-')
}
hours := absMicroseconds / microsecondsPerHour
minutes := (absMicroseconds % microsecondsPerHour) / microsecondsPerMinute
seconds := (absMicroseconds % microsecondsPerMinute) / microsecondsPerSecond
timeStr := fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
buf = append(buf, timeStr...)
microseconds := absMicroseconds % microsecondsPerSecond
if microseconds != 0 {
buf = append(buf, fmt.Sprintf(".%06d", microseconds)...)
}
return buf, nil
}
func (IntervalCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, target any) pgtype.ScanPlan {
switch format {
case pgtype.BinaryFormatCode:
switch target.(type) {
case IntervalScanner:
return scanPlanBinaryIntervalToIntervalScanner{}
}
case pgtype.TextFormatCode:
switch target.(type) {
case IntervalScanner:
return scanPlanTextAnyToIntervalScanner{}
}
}
return nil
}
type scanPlanBinaryIntervalToIntervalScanner struct{}
func (scanPlanBinaryIntervalToIntervalScanner) Scan(src []byte, dst any) error {
scanner := (dst).(IntervalScanner)
if src == nil {
return scanner.ScanInterval(Interval{})
}
if len(src) != 16 {
return fmt.Errorf("Received an invalid size for an interval: %d", len(src))
}
microseconds := int64(binary.BigEndian.Uint64(src))
days := int32(binary.BigEndian.Uint32(src[8:]))
months := int32(binary.BigEndian.Uint32(src[12:]))
return scanner.ScanInterval(Interval{Microseconds: microseconds, Days: days, Months: months, Valid: true})
}
type scanPlanTextAnyToIntervalScanner struct{}
func (scanPlanTextAnyToIntervalScanner) Scan(src []byte, dst any) error {
scanner := (dst).(IntervalScanner)
if src == nil {
return scanner.ScanInterval(Interval{})
}
var microseconds int64
var days int32
var months int32
parts := strings.Split(string(src), " ")
for i := 0; i < len(parts)-1; i += 2 {
scalar, err := strconv.ParseInt(parts[i], 10, 64)
if err != nil {
return fmt.Errorf("bad interval format")
}
switch parts[i+1] {
case "year", "years":
months += int32(scalar * 12)
case "mon", "mons":
months += int32(scalar)
case "day", "days":
days = int32(scalar)
}
}
if len(parts)%2 == 1 {
timeParts := strings.SplitN(parts[len(parts)-1], ":", 3)
if len(timeParts) != 3 {
return fmt.Errorf("bad interval format")
}
var negative bool
if timeParts[0][0] == '-' {
negative = true
timeParts[0] = timeParts[0][1:]
}
hours, err := strconv.ParseInt(timeParts[0], 10, 64)
if err != nil {
return fmt.Errorf("bad interval hour format: %s", timeParts[0])
}
minutes, err := strconv.ParseInt(timeParts[1], 10, 64)
if err != nil {
return fmt.Errorf("bad interval minute format: %s", timeParts[1])
}
sec, secFrac, secFracFound := strings.Cut(timeParts[2], ".")
seconds, err := strconv.ParseInt(sec, 10, 64)
if err != nil {
return fmt.Errorf("bad interval second format: %s", sec)
}
var uSeconds int64
if secFracFound {
uSeconds, err = strconv.ParseInt(secFrac, 10, 64)
if err != nil {
return fmt.Errorf("bad interval decimal format: %s", secFrac)
}
for i := 0; i < 6-len(secFrac); i++ {
uSeconds *= 10
}
}
microseconds = hours * microsecondsPerHour
microseconds += minutes * microsecondsPerMinute
microseconds += seconds * microsecondsPerSecond
microseconds += uSeconds
if negative {
microseconds = -microseconds
}
}
return scanner.ScanInterval(Interval{Months: months, Days: days, Microseconds: microseconds, Valid: true})
}
func (c IntervalCodec) DecodeDatabaseSQLValue(m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
return codecDecodeToTextFormat(c, m, oid, format, src)
}
func (c IntervalCodec) DecodeValue(m *pgtype.Map, oid uint32, format int16, src []byte) (any, error) {
if src == nil {
return nil, nil
}
var interval Interval
err := codecScan(c, m, oid, format, src, &interval)
if err != nil {
return nil, err
}
return interval, nil
}

145
postgres_pgtype/pgtype.go Normal file
View File

@@ -0,0 +1,145 @@
// копия файла из https://github.com/jackc/pgtype/pgtype.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgtype
import (
"database/sql/driver"
"github.com/jackc/pgx/v5/pgtype"
)
// PostgreSQL oids for common types
const (
BoolOID = 16
ByteaOID = 17
QCharOID = 18
NameOID = 19
Int8OID = 20
Int2OID = 21
Int4OID = 23
TextOID = 25
OIDOID = 26
TIDOID = 27
XIDOID = 28
CIDOID = 29
JSONOID = 114
XMLOID = 142
XMLArrayOID = 143
JSONArrayOID = 199
XID8ArrayOID = 271
PointOID = 600
LsegOID = 601
PathOID = 602
BoxOID = 603
PolygonOID = 604
LineOID = 628
LineArrayOID = 629
CIDROID = 650
CIDRArrayOID = 651
Float4OID = 700
Float8OID = 701
CircleOID = 718
CircleArrayOID = 719
UnknownOID = 705
Macaddr8OID = 774
MacaddrOID = 829
InetOID = 869
BoolArrayOID = 1000
QCharArrayOID = 1002
NameArrayOID = 1003
Int2ArrayOID = 1005
Int4ArrayOID = 1007
TextArrayOID = 1009
TIDArrayOID = 1010
ByteaArrayOID = 1001
XIDArrayOID = 1011
CIDArrayOID = 1012
BPCharArrayOID = 1014
VarcharArrayOID = 1015
Int8ArrayOID = 1016
PointArrayOID = 1017
LsegArrayOID = 1018
PathArrayOID = 1019
BoxArrayOID = 1020
Float4ArrayOID = 1021
Float8ArrayOID = 1022
PolygonArrayOID = 1027
OIDArrayOID = 1028
ACLItemOID = 1033
ACLItemArrayOID = 1034
MacaddrArrayOID = 1040
InetArrayOID = 1041
BPCharOID = 1042
VarcharOID = 1043
DateOID = 1082
TimeOID = 1083
TimestampOID = 1114
TimestampArrayOID = 1115
DateArrayOID = 1182
TimeArrayOID = 1183
TimestamptzOID = 1184
TimestamptzArrayOID = 1185
IntervalOID = 1186
IntervalArrayOID = 1187
NumericArrayOID = 1231
TimetzOID = 1266
TimetzArrayOID = 1270
BitOID = 1560
BitArrayOID = 1561
VarbitOID = 1562
VarbitArrayOID = 1563
NumericOID = 1700
RecordOID = 2249
RecordArrayOID = 2287
UUIDOID = 2950
UUIDArrayOID = 2951
JSONBOID = 3802
JSONBArrayOID = 3807
DaterangeOID = 3912
DaterangeArrayOID = 3913
Int4rangeOID = 3904
Int4rangeArrayOID = 3905
NumrangeOID = 3906
NumrangeArrayOID = 3907
TsrangeOID = 3908
TsrangeArrayOID = 3909
TstzrangeOID = 3910
TstzrangeArrayOID = 3911
Int8rangeOID = 3926
Int8rangeArrayOID = 3927
JSONPathOID = 4072
JSONPathArrayOID = 4073
Int4multirangeOID = 4451
NummultirangeOID = 4532
TsmultirangeOID = 4533
TstzmultirangeOID = 4534
DatemultirangeOID = 4535
Int8multirangeOID = 4536
XID8OID = 5069
Int4multirangeArrayOID = 6150
NummultirangeArrayOID = 6151
TsmultirangeArrayOID = 6152
TstzmultirangeArrayOID = 6153
DatemultirangeArrayOID = 6155
Int8multirangeArrayOID = 6157
)
func codecDecodeToTextFormat(codec pgtype.Codec, m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
if src == nil {
return nil, nil
}
if format == pgtype.TextFormatCode {
return string(src), nil
} else {
value, err := codec.DecodeValue(m, oid, format, src)
if err != nil {
return nil, err
}
buf, err := m.Encode(oid, pgtype.TextFormatCode, value, nil)
if err != nil {
return nil, err
}
return string(buf), nil
}
}

View File

@@ -0,0 +1,4 @@
// копия файла из https://github.com/jackc/pgtype/pgtype_default.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgtype

8
postgres_pgtype/sanek.go Normal file
View File

@@ -0,0 +1,8 @@
package postgres_pgtype
import "reflect"
// getInterfaceName - возвращает имя типа интерфейса
func getInterfaceName(v interface{}) string {
return reflect.TypeOf(v).String()
}

279
postgres_pgtype/time.go Normal file
View File

@@ -0,0 +1,279 @@
// копия файла из https://github.com/jackc/pgtype/timestamptz.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgtype
import (
"database/sql/driver"
"encoding/binary"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
"strconv"
)
//type TimeScanner interface {
// ScanTime(v Time) error
//}
//
//type TimeValuer interface {
// TimeValue() (Time, error)
//}
// Time represents the PostgreSQL time type. The PostgreSQL time is a time of day without time zone.
//
// Time is represented as the number of microseconds since midnight in the same way that PostgreSQL does. Other time and
// date types in pgtype can use time.Time as the underlying representation. However, pgtype.Time type cannot due to
// needing to handle 24:00:00. time.Time converts that to 00:00:00 on the following day.
//
// The time with time zone type is not supported. Use of time with time zone is discouraged by the PostgreSQL documentation.
type Time struct {
Microseconds int64 // Number of microseconds since midnight
Valid bool
}
func (t *Time) ScanTime(v Time) error {
*t = v
return nil
}
func (t Time) TimeValue() (Time, error) {
return t, nil
}
// Scan implements the database/sql Scanner interface.
func (t *Time) Scan(src any) error {
if src == nil {
*t = Time{Valid: true} //sanek
//*t = Time{}
return nil
}
switch src := src.(type) {
case string:
err := scanPlanTextAnyToTimeScanner{}.Scan([]byte(src), t)
if err != nil {
t.Microseconds = 0
t.Valid = false
}
return err
}
return fmt.Errorf("cannot scan %T", src)
}
// Value implements the database/sql/driver Valuer interface.
func (t Time) Value() (driver.Value, error) {
if !t.Valid {
return nil, nil
}
buf, err := pgtype.TimeCodec{}.PlanEncode(nil, 0, pgtype.TextFormatCode, t).Encode(t, nil)
if err != nil {
return nil, err
}
return string(buf), err
}
type TimeCodec struct{}
func (TimeCodec) FormatSupported(format int16) bool {
return format == pgtype.TextFormatCode || format == pgtype.BinaryFormatCode
}
func (TimeCodec) PreferredFormat() int16 {
return pgtype.BinaryFormatCode
}
func (TimeCodec) PlanEncode(m *pgtype.Map, oid uint32, format int16, value any) pgtype.EncodePlan {
if _, ok := value.(pgtype.TimeValuer); !ok {
return nil
}
switch format {
case pgtype.BinaryFormatCode:
return encodePlanTimeCodecBinary{}
case pgtype.TextFormatCode:
return encodePlanTimeCodecText{}
}
return nil
}
type encodePlanTimeCodecBinary struct{}
func (encodePlanTimeCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
t, err := value.(pgtype.TimeValuer).TimeValue()
if err != nil {
return nil, err
}
if !t.Valid {
return nil, nil
}
return AppendInt64(buf, t.Microseconds), nil
}
type encodePlanTimeCodecText struct{}
func (encodePlanTimeCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
t, err := value.(pgtype.TimeValuer).TimeValue()
if err != nil {
return nil, err
}
if !t.Valid {
return nil, nil
}
usec := t.Microseconds
hours := usec / microsecondsPerHour
usec -= hours * microsecondsPerHour
minutes := usec / microsecondsPerMinute
usec -= minutes * microsecondsPerMinute
seconds := usec / microsecondsPerSecond
usec -= seconds * microsecondsPerSecond
s := fmt.Sprintf("%02d:%02d:%02d.%06d", hours, minutes, seconds, usec)
return append(buf, s...), nil
}
func (TimeCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, target any) pgtype.ScanPlan {
switch format {
case pgtype.BinaryFormatCode:
name := getInterfaceName(target) //sanek
switch name {
case "*pgtype.timeWrapper":
return scanPlanBinaryTimeToTimeScanner{}
case "*pgtype.stringWrapper":
return scanPlanBinaryTimeToTextScanner{}
}
case pgtype.TextFormatCode:
name := getInterfaceName(target) //sanek
switch name {
case "*pgtype.timeWrapper":
return scanPlanTextAnyToTimeScanner{}
}
}
return nil
}
type scanPlanBinaryTimeToTimeScanner struct{}
func (scanPlanBinaryTimeToTimeScanner) Scan(src []byte, dst any) error {
scanner := (dst).(pgtype.TimeScanner)
if src == nil {
return scanner.ScanTime(pgtype.Time{})
}
if len(src) != 8 {
return fmt.Errorf("invalid length for time: %v", len(src))
}
usec := int64(binary.BigEndian.Uint64(src))
return scanner.ScanTime(pgtype.Time{Microseconds: usec, Valid: true})
}
type scanPlanBinaryTimeToTextScanner struct{}
func (scanPlanBinaryTimeToTextScanner) Scan(src []byte, dst any) error {
ts, ok := (dst).(pgtype.TextScanner)
if !ok {
return pgtype.ErrScanTargetTypeChanged
}
if src == nil {
return ts.ScanText(pgtype.Text{})
}
if len(src) != 8 {
return fmt.Errorf("invalid length for time: %v", len(src))
}
usec := int64(binary.BigEndian.Uint64(src))
tim := Time{Microseconds: usec, Valid: true}
buf, err := TimeCodec{}.PlanEncode(nil, 0, pgtype.TextFormatCode, tim).Encode(tim, nil)
if err != nil {
return err
}
return ts.ScanText(pgtype.Text{String: string(buf), Valid: true})
}
type scanPlanTextAnyToTimeScanner struct{}
func (scanPlanTextAnyToTimeScanner) Scan(src []byte, dst any) error {
scanner := (dst).(pgtype.TimeScanner)
if src == nil {
return scanner.ScanTime(pgtype.Time{})
}
s := string(src)
if len(s) < 8 || s[2] != ':' || s[5] != ':' {
return fmt.Errorf("cannot decode %v into Time", s)
}
hours, err := strconv.ParseInt(s[0:2], 10, 64)
if err != nil {
return fmt.Errorf("cannot decode %v into Time", s)
}
usec := hours * microsecondsPerHour
minutes, err := strconv.ParseInt(s[3:5], 10, 64)
if err != nil {
return fmt.Errorf("cannot decode %v into Time", s)
}
usec += minutes * microsecondsPerMinute
seconds, err := strconv.ParseInt(s[6:8], 10, 64)
if err != nil {
return fmt.Errorf("cannot decode %v into Time", s)
}
usec += seconds * microsecondsPerSecond
if len(s) > 9 {
if s[8] != '.' || len(s) > 15 {
return fmt.Errorf("cannot decode %v into Time", s)
}
fraction := s[9:]
n, err := strconv.ParseInt(fraction, 10, 64)
if err != nil {
return fmt.Errorf("cannot decode %v into Time", s)
}
for i := len(fraction); i < 6; i++ {
n *= 10
}
usec += n
}
return scanner.ScanTime(pgtype.Time{Microseconds: usec, Valid: true})
}
func (c TimeCodec) DecodeDatabaseSQLValue(m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
return codecDecodeToTextFormat(c, m, oid, format, src)
}
func (c TimeCodec) DecodeValue(m *pgtype.Map, oid uint32, format int16, src []byte) (any, error) {
if src == nil {
return nil, nil
}
var t Time
err := codecScan(c, m, oid, format, src, &t)
if err != nil {
return nil, err
}
return t, nil
}

View File

@@ -0,0 +1,361 @@
// копия файла из https://github.com/jackc/pgtype/timestamp.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgtype
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
"strings"
"time"
//"github.com/jackc/pgx/v5/internal/pgio"
)
const pgTimestampFormat = "2006-01-02 15:04:05.999999999"
//type TimestampScanner interface {
// ScanTimestamp(v Timestamp) error
//}
//
//type TimestampValuer interface {
// TimestampValue() (Timestamp, error)
//}
// Timestamp represents the PostgreSQL timestamp type.
type Timestamp struct {
Time time.Time // Time zone will be ignored when encoding to PostgreSQL.
InfinityModifier pgtype.InfinityModifier
Valid bool
}
func (ts *Timestamp) ScanTimestamp(v Timestamp) error {
*ts = v
return nil
}
func (ts Timestamp) TimestampValue() (Timestamp, error) {
return ts, nil
}
// Scan implements the database/sql Scanner interface.
func (ts *Timestamp) Scan(src any) error {
if src == nil {
*ts = Timestamp{Valid: true} //sanek
//*ts = Timestamp{}
return nil
}
switch src := src.(type) {
case string:
return (&scanPlanTextTimestampToTimestampScanner{}).Scan([]byte(src), ts)
case time.Time:
*ts = Timestamp{Time: src, Valid: true}
return nil
}
return fmt.Errorf("cannot scan %T", src)
}
// Value implements the database/sql/driver Valuer interface.
func (ts Timestamp) Value() (driver.Value, error) {
if !ts.Valid {
return nil, nil
}
if ts.InfinityModifier != pgtype.Finite {
return ts.InfinityModifier.String(), nil
}
return ts.Time, nil
}
func (ts Timestamp) MarshalJSON() ([]byte, error) {
if !ts.Valid {
return []byte("null"), nil
}
var s string
switch ts.InfinityModifier {
case pgtype.Finite:
s = ts.Time.Format(time.RFC3339Nano)
case pgtype.Infinity:
s = "infinity"
case pgtype.NegativeInfinity:
s = "-infinity"
}
return json.Marshal(s)
}
func (ts *Timestamp) UnmarshalJSON(b []byte) error {
var s *string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
if s == nil {
*ts = Timestamp{}
return nil
}
switch *s {
case "infinity":
*ts = Timestamp{Valid: true, InfinityModifier: pgtype.Infinity}
case "-infinity":
*ts = Timestamp{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
// PostgreSQL uses ISO 8601 wihout timezone for to_json function and casting from a string to timestampt
tim, err := time.Parse(time.RFC3339Nano, *s+"Z")
if err != nil {
return err
}
*ts = Timestamp{Time: tim, Valid: true}
}
return nil
}
type TimestampCodec struct {
// ScanLocation is the location that the time is assumed to be in for scanning. This is different from
// TimestamptzCodec.ScanLocation in that this setting does change the instant in time that the timestamp represents.
ScanLocation *time.Location
}
func (*TimestampCodec) FormatSupported(format int16) bool {
return format == pgtype.TextFormatCode || format == pgtype.BinaryFormatCode
}
func (*TimestampCodec) PreferredFormat() int16 {
return pgtype.BinaryFormatCode
}
func (*TimestampCodec) PlanEncode(m *pgtype.Map, oid uint32, format int16, value any) pgtype.EncodePlan {
if _, ok := value.(pgtype.TimestampValuer); !ok {
return nil
}
switch format {
case pgtype.BinaryFormatCode:
return encodePlanTimestampCodecBinary{}
case pgtype.TextFormatCode:
return encodePlanTimestampCodecText{}
}
return nil
}
type encodePlanTimestampCodecBinary struct{}
func (encodePlanTimestampCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
ts, err := value.(pgtype.TimestampValuer).TimestampValue()
if err != nil {
return nil, err
}
if !ts.Valid {
return nil, nil
}
var microsecSinceY2K int64
switch ts.InfinityModifier {
case pgtype.Finite:
t := discardTimeZone(ts.Time)
microsecSinceUnixEpoch := t.Unix()*1000000 + int64(t.Nanosecond())/1000
microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
case pgtype.Infinity:
microsecSinceY2K = infinityMicrosecondOffset
case pgtype.NegativeInfinity:
microsecSinceY2K = negativeInfinityMicrosecondOffset
}
buf = AppendInt64(buf, microsecSinceY2K)
return buf, nil
}
type encodePlanTimestampCodecText struct{}
func (encodePlanTimestampCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
ts, err := value.(pgtype.TimestampValuer).TimestampValue()
if err != nil {
return nil, err
}
if !ts.Valid {
return nil, nil
}
var s string
switch ts.InfinityModifier {
case pgtype.Finite:
t := discardTimeZone(ts.Time)
// 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.Truncate(time.Microsecond).Format(pgTimestampFormat)
if bc {
s = s + " BC"
}
case pgtype.Infinity:
s = "infinity"
case pgtype.NegativeInfinity:
s = "-infinity"
}
buf = append(buf, s...)
return buf, nil
}
func discardTimeZone(t time.Time) time.Time {
if t.Location() != time.UTC {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
}
return t
}
func (c *TimestampCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, target any) pgtype.ScanPlan {
switch format {
case pgtype.BinaryFormatCode:
name := getInterfaceName(target) //sanek
switch name {
case "*pgtype.timeWrapper":
return &scanPlanBinaryTimestampToTimestampScanner{location: c.ScanLocation}
}
case pgtype.TextFormatCode:
name := getInterfaceName(target) //sanek
switch name {
case "*pgtype.timeWrapper":
return &scanPlanTextTimestampToTimestampScanner{location: c.ScanLocation}
}
}
return nil
}
type scanPlanBinaryTimestampToTimestampScanner struct{ location *time.Location }
func (plan *scanPlanBinaryTimestampToTimestampScanner) Scan(src []byte, dst any) error {
scanner := (dst).(pgtype.TimestampScanner)
if src == nil {
return scanner.ScanTimestamp(pgtype.Timestamp{})
}
if len(src) != 8 {
return fmt.Errorf("invalid length for timestamp: %v", len(src))
}
var ts pgtype.Timestamp
microsecSinceY2K := int64(binary.BigEndian.Uint64(src))
switch microsecSinceY2K {
case infinityMicrosecondOffset:
ts = pgtype.Timestamp{Valid: true, InfinityModifier: pgtype.Infinity}
case negativeInfinityMicrosecondOffset:
ts = pgtype.Timestamp{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
tim := time.Unix(
microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000,
(microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000),
).UTC()
if plan.location != nil {
tim = time.Date(tim.Year(), tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), plan.location)
}
ts = pgtype.Timestamp{Time: tim, Valid: true}
}
return scanner.ScanTimestamp(ts)
}
type scanPlanTextTimestampToTimestampScanner struct{ location *time.Location }
func (plan *scanPlanTextTimestampToTimestampScanner) Scan(src []byte, dst any) error {
scanner := (dst).(pgtype.TimestampScanner)
if src == nil {
return scanner.ScanTimestamp(pgtype.Timestamp{})
}
var ts pgtype.Timestamp
sbuf := string(src)
switch sbuf {
case "infinity":
ts = pgtype.Timestamp{Valid: true, InfinityModifier: pgtype.Infinity}
case "-infinity":
ts = pgtype.Timestamp{Valid: true, InfinityModifier: -pgtype.Infinity}
default:
bc := false
if strings.HasSuffix(sbuf, " BC") {
sbuf = sbuf[:len(sbuf)-3]
bc = true
}
tim, err := time.Parse(pgTimestampFormat, 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 = time.Date(tim.Year(), tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), plan.location)
}
ts = pgtype.Timestamp{Time: tim, Valid: true}
}
return scanner.ScanTimestamp(ts)
}
func (c *TimestampCodec) DecodeDatabaseSQLValue(m *pgtype.Map, oid uint32, format int16, src []byte) (driver.Value, error) {
if src == nil {
return nil, nil
}
var ts Timestamp
err := codecScan(c, m, oid, format, src, &ts)
if err != nil {
return nil, err
}
if ts.InfinityModifier != pgtype.Finite {
return ts.InfinityModifier.String(), nil
}
return ts.Time, nil
}
func (c *TimestampCodec) DecodeValue(m *pgtype.Map, oid uint32, format int16, src []byte) (any, error) {
if src == nil {
return nil, nil
}
var ts Timestamp
err := codecScan(c, m, oid, format, src, &ts)
if err != nil {
return nil, err
}
if ts.InfinityModifier != pgtype.Finite {
return ts.InfinityModifier, nil
}
return ts.Time, nil
}

View File

@@ -1,7 +1,7 @@
// копия файла из https://github.com/jackc/pgtype/timestamptz.go
// чтоб не выдавала ошибку на null
// чтобы дата NULL = time.Time{}
package postgres_pgx
package postgres_pgtype
import (
"database/sql/driver"
@@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
"reflect"
"strings"
"time"
)
@@ -248,10 +247,6 @@ func (c *TimestamptzCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, tar
return nil
}
func getInterfaceName(v interface{}) string {
return reflect.TypeOf(v).String()
}
type scanPlanBinaryTimestamptzToTimestamptzScanner struct{ location *time.Location }
func (plan *scanPlanBinaryTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {

View File

@@ -1,4 +1,4 @@
package postgres_pgx
package postgres_pgtype
import "encoding/binary"

View File

@@ -9,6 +9,7 @@ import (
"github.com/ManyakRus/starter/constants"
"github.com/ManyakRus/starter/log"
"github.com/ManyakRus/starter/port_checker"
"github.com/ManyakRus/starter/postgres_pgtype"
"github.com/ManyakRus/starter/postgres_pgx"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
@@ -589,12 +590,44 @@ func ReplaceSchemaName(TextSQL, SchemaNameFrom string) string {
return Otvet
}
// AfterConnect_NoNull - регистрирует обработчики для нужных типов
// чтобы NULL=default value
func AfterConnect_NoNull(ctx context.Context, conn *pgx.Conn) error {
// Регистрируем zeronull обработчики для нужных типов
// Регистрируем обработчики для нужных типов,
//timestamptz
conn.TypeMap().RegisterType(&pgtype.Type{
Name: "timestamptz",
OID: pgtype.TimestamptzOID,
Codec: &postgres_pgx.TimestamptzCodec{},
Codec: &postgres_pgtype.TimestamptzCodec{},
})
//timestamp
conn.TypeMap().RegisterType(&pgtype.Type{
Name: "timestamp",
OID: pgtype.TimestampOID,
Codec: &postgres_pgtype.TimestampCodec{},
})
//timetz
conn.TypeMap().RegisterType(&pgtype.Type{
Name: "timetz",
OID: pgtype.TimetzOID,
Codec: &postgres_pgtype.TimeCodec{},
})
//time
conn.TypeMap().RegisterType(&pgtype.Type{
Name: "time",
OID: pgtype.TimeOID,
Codec: &postgres_pgtype.TimeCodec{},
})
//date
conn.TypeMap().RegisterType(&pgtype.Type{
Name: "date",
OID: pgtype.DateOID,
Codec: &postgres_pgtype.DateCodec{},
})
return nil