mirror of
https://github.com/ManyakRus/starter.git
synced 2025-11-25 23:02:22 +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
|
// копия файла из https://github.com/jackc/pgtype/timestamptz.go
|
||||||
// чтоб не выдавала ошибку на null
|
// чтоб не выдавала ошибку на null
|
||||||
// чтобы дата NULL = time.Time{}
|
// чтобы дата NULL = time.Time{}
|
||||||
package postgres_pgx
|
package postgres_pgtype
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -248,10 +247,6 @@ func (c *TimestamptzCodec) PlanScan(m *pgtype.Map, oid uint32, format int16, tar
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInterfaceName(v interface{}) string {
|
|
||||||
return reflect.TypeOf(v).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type scanPlanBinaryTimestamptzToTimestamptzScanner struct{ location *time.Location }
|
type scanPlanBinaryTimestamptzToTimestamptzScanner struct{ location *time.Location }
|
||||||
|
|
||||||
func (plan *scanPlanBinaryTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {
|
func (plan *scanPlanBinaryTimestamptzToTimestamptzScanner) Scan(src []byte, dst any) error {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package postgres_pgx
|
package postgres_pgtype
|
||||||
|
|
||||||
import "encoding/binary"
|
import "encoding/binary"
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/ManyakRus/starter/constants"
|
"github.com/ManyakRus/starter/constants"
|
||||||
"github.com/ManyakRus/starter/log"
|
"github.com/ManyakRus/starter/log"
|
||||||
"github.com/ManyakRus/starter/port_checker"
|
"github.com/ManyakRus/starter/port_checker"
|
||||||
|
"github.com/ManyakRus/starter/postgres_pgtype"
|
||||||
"github.com/ManyakRus/starter/postgres_pgx"
|
"github.com/ManyakRus/starter/postgres_pgx"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
@@ -589,12 +590,44 @@ func ReplaceSchemaName(TextSQL, SchemaNameFrom string) string {
|
|||||||
return Otvet
|
return Otvet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AfterConnect_NoNull - регистрирует обработчики для нужных типов
|
||||||
|
// чтобы NULL=default value
|
||||||
func AfterConnect_NoNull(ctx context.Context, conn *pgx.Conn) error {
|
func AfterConnect_NoNull(ctx context.Context, conn *pgx.Conn) error {
|
||||||
// Регистрируем zeronull обработчики для нужных типов
|
// Регистрируем обработчики для нужных типов,
|
||||||
|
|
||||||
|
//timestamptz
|
||||||
conn.TypeMap().RegisterType(&pgtype.Type{
|
conn.TypeMap().RegisterType(&pgtype.Type{
|
||||||
Name: "timestamptz",
|
Name: "timestamptz",
|
||||||
OID: pgtype.TimestamptzOID,
|
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
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user