mirror of
https://github.com/ManyakRus/starter.git
synced 2025-11-24 22:53:52 +02:00
сделал postgres_pgtype
This commit is contained in:
356
postgres_pgtype/date.go
Normal file
356
postgres_pgtype/date.go
Normal 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
299
postgres_pgtype/interval.go
Normal 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
145
postgres_pgtype/pgtype.go
Normal 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
|
||||
}
|
||||
}
|
||||
4
postgres_pgtype/pgtype_default.go
Normal file
4
postgres_pgtype/pgtype_default.go
Normal 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
8
postgres_pgtype/sanek.go
Normal 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
279
postgres_pgtype/time.go
Normal 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
|
||||
}
|
||||
361
postgres_pgtype/timestamp.go
Normal file
361
postgres_pgtype/timestamp.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -1,4 +1,4 @@
|
||||
package postgres_pgx
|
||||
package postgres_pgtype
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user