github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/types.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package pgwire
    12  
    13  import (
    14  	"context"
    15  	"encoding/binary"
    16  	"math"
    17  	"math/big"
    18  	"net"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/cockroachdb/apd"
    24  	"github.com/cockroachdb/cockroach/pkg/server/telemetry"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/lex"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgwirebase"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    28  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    29  	"github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry"
    30  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    31  	"github.com/cockroachdb/cockroach/pkg/util/duration"
    32  	"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
    33  	"github.com/cockroachdb/cockroach/pkg/util/ipaddr"
    34  	"github.com/cockroachdb/cockroach/pkg/util/log"
    35  	"github.com/cockroachdb/cockroach/pkg/util/timeofday"
    36  	"github.com/cockroachdb/cockroach/pkg/util/timetz"
    37  	"github.com/cockroachdb/errors"
    38  	"github.com/lib/pq/oid"
    39  )
    40  
    41  // pgType contains type metadata used in RowDescription messages.
    42  type pgType struct {
    43  	oid oid.Oid
    44  
    45  	// Variable-size types have size=-1.
    46  	// Note that the protocol has both int16 and int32 size fields,
    47  	// so this attribute is an unsized int and should be cast
    48  	// as needed.
    49  	// This field does *not* correspond to the encoded length of a
    50  	// data type, so it's unclear what, if anything, it is used for.
    51  	// To get the right value, "SELECT oid, typlen FROM pg_type"
    52  	// on a postgres server.
    53  	size int
    54  }
    55  
    56  func pgTypeForParserType(t *types.T) pgType {
    57  	size := -1
    58  	if s, variable := tree.DatumTypeSize(t); !variable {
    59  		size = int(s)
    60  	}
    61  	return pgType{
    62  		oid:  t.Oid(),
    63  		size: size,
    64  	}
    65  }
    66  
    67  func (b *writeBuffer) writeTextDatum(
    68  	ctx context.Context, d tree.Datum, conv sessiondata.DataConversionConfig,
    69  ) {
    70  	if log.V(2) {
    71  		log.Infof(ctx, "pgwire writing TEXT datum of type: %T, %#v", d, d)
    72  	}
    73  	if d == tree.DNull {
    74  		// NULL is encoded as -1; all other values have a length prefix.
    75  		b.putInt32(-1)
    76  		return
    77  	}
    78  	switch v := tree.UnwrapDatum(nil, d).(type) {
    79  	case *tree.DBitArray:
    80  		b.textFormatter.FormatNode(v)
    81  		b.writeFromFmtCtx(b.textFormatter)
    82  
    83  	case *tree.DBool:
    84  		b.textFormatter.FormatNode(v)
    85  		b.writeFromFmtCtx(b.textFormatter)
    86  
    87  	case *tree.DInt:
    88  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
    89  		s := strconv.AppendInt(b.putbuf[4:4], int64(*v), 10)
    90  		b.putInt32(int32(len(s)))
    91  		b.write(s)
    92  
    93  	case *tree.DFloat:
    94  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
    95  		s := strconv.AppendFloat(b.putbuf[4:4], float64(*v), 'g', conv.GetFloatPrec(), 64)
    96  		b.putInt32(int32(len(s)))
    97  		b.write(s)
    98  
    99  	case *tree.DDecimal:
   100  		b.writeLengthPrefixedDatum(v)
   101  
   102  	case *tree.DBytes:
   103  		result := lex.EncodeByteArrayToRawBytes(
   104  			string(*v), conv.BytesEncodeFormat, false /* skipHexPrefix */)
   105  		b.putInt32(int32(len(result)))
   106  		b.write([]byte(result))
   107  
   108  	case *tree.DUuid:
   109  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
   110  		s := b.putbuf[4 : 4+36]
   111  		v.UUID.StringBytes(s)
   112  		b.putInt32(int32(len(s)))
   113  		b.write(s)
   114  
   115  	case *tree.DIPAddr:
   116  		b.writeLengthPrefixedString(v.IPAddr.String())
   117  
   118  	case *tree.DString:
   119  		b.writeLengthPrefixedString(string(*v))
   120  
   121  	case *tree.DCollatedString:
   122  		b.writeLengthPrefixedString(v.Contents)
   123  
   124  	case *tree.DDate:
   125  		b.textFormatter.FormatNode(v)
   126  		b.writeFromFmtCtx(b.textFormatter)
   127  
   128  	case *tree.DTime:
   129  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
   130  		s := formatTime(timeofday.TimeOfDay(*v), b.putbuf[4:4])
   131  		b.putInt32(int32(len(s)))
   132  		b.write(s)
   133  
   134  	case *tree.DTimeTZ:
   135  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
   136  		s := formatTimeTZ(v.TimeTZ, b.putbuf[4:4])
   137  		b.putInt32(int32(len(s)))
   138  		b.write(s)
   139  
   140  	case *tree.DGeography:
   141  		s := v.Geography.EWKBHex()
   142  		b.putInt32(int32(len(s)))
   143  		b.write([]byte(s))
   144  
   145  	case *tree.DGeometry:
   146  		s := v.Geometry.EWKBHex()
   147  		b.putInt32(int32(len(s)))
   148  		b.write([]byte(s))
   149  
   150  	case *tree.DTimestamp:
   151  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
   152  		s := formatTs(v.Time, nil, b.putbuf[4:4])
   153  		b.putInt32(int32(len(s)))
   154  		b.write(s)
   155  
   156  	case *tree.DTimestampTZ:
   157  		// Start at offset 4 because `putInt32` clobbers the first 4 bytes.
   158  		s := formatTs(v.Time, conv.Location, b.putbuf[4:4])
   159  		b.putInt32(int32(len(s)))
   160  		b.write(s)
   161  
   162  	case *tree.DInterval:
   163  		b.textFormatter.FormatNode(v)
   164  		b.writeFromFmtCtx(b.textFormatter)
   165  
   166  	case *tree.DJSON:
   167  		b.writeLengthPrefixedString(v.JSON.String())
   168  
   169  	case *tree.DTuple:
   170  		b.textFormatter.FormatNode(v)
   171  		b.writeFromFmtCtx(b.textFormatter)
   172  
   173  	case *tree.DArray:
   174  		// Arrays have custom formatting depending on their OID.
   175  		b.textFormatter.FormatNode(d)
   176  		b.writeFromFmtCtx(b.textFormatter)
   177  
   178  	case *tree.DOid:
   179  		b.writeLengthPrefixedDatum(v)
   180  
   181  	case *tree.DEnum:
   182  		// Enums are serialized with their logical representation.
   183  		b.writeLengthPrefixedString(v.LogicalRep)
   184  
   185  	default:
   186  		b.setError(errors.Errorf("unsupported type %T", d))
   187  	}
   188  }
   189  
   190  // writeBinaryDatum writes d to the buffer. Oid must be specified for types
   191  // that have various width encodings. It is ignored (and can be 0) for types
   192  // with a 1:1 datum:oid mapping.
   193  func (b *writeBuffer) writeBinaryDatum(
   194  	ctx context.Context, d tree.Datum, sessionLoc *time.Location, Oid oid.Oid,
   195  ) {
   196  	if log.V(2) {
   197  		log.Infof(ctx, "pgwire writing BINARY datum of type: %T, %#v", d, d)
   198  	}
   199  	if d == tree.DNull {
   200  		// NULL is encoded as -1; all other values have a length prefix.
   201  		b.putInt32(-1)
   202  		return
   203  	}
   204  	switch v := tree.UnwrapDatum(nil, d).(type) {
   205  	case *tree.DBitArray:
   206  		words, lastBitsUsed := v.EncodingParts()
   207  		if len(words) == 0 {
   208  			b.putInt32(4)
   209  		} else {
   210  			// Encode the length of the output bytes. It is computed here so we don't
   211  			// have to keep a buffer.
   212  			// 4: the int32 of the bitLen.
   213  			// 8*(len(words)-1): number of 8-byte words except the last one since it's
   214  			//   partial.
   215  			// (lastBitsUsed+7)/8: number of bytes that will be written in the last
   216  			//   partial word. The /8 rounds down, such that the +7 will cause 1-or-more
   217  			//   bits to use a byte, but 0 will not.
   218  			b.putInt32(4 + int32(8*(len(words)-1)) + int32((lastBitsUsed+7)/8))
   219  		}
   220  		bitLen := v.BitLen()
   221  		b.putInt32(int32(bitLen))
   222  		var byteBuf [8]byte
   223  		for i := 0; i < len(words)-1; i++ {
   224  			w := words[i]
   225  			binary.BigEndian.PutUint64(byteBuf[:], w)
   226  			b.write(byteBuf[:])
   227  		}
   228  		if len(words) > 0 {
   229  			w := words[len(words)-1]
   230  			for i := uint(0); i < uint(lastBitsUsed); i += 8 {
   231  				c := byte(w >> (56 - i))
   232  				b.writeByte(c)
   233  			}
   234  		}
   235  
   236  	case *tree.DBool:
   237  		b.putInt32(1)
   238  		if *v {
   239  			b.writeByte(1)
   240  		} else {
   241  			b.writeByte(0)
   242  		}
   243  
   244  	case *tree.DInt:
   245  		switch Oid {
   246  		case oid.T_int2:
   247  			b.putInt32(2)
   248  			b.putInt16(int16(*v))
   249  		case oid.T_int4:
   250  			b.putInt32(4)
   251  			b.putInt32(int32(*v))
   252  		case oid.T_int8:
   253  			b.putInt32(8)
   254  			b.putInt64(int64(*v))
   255  		default:
   256  			b.setError(errors.Errorf("unsupported int oid: %v", Oid))
   257  		}
   258  
   259  	case *tree.DFloat:
   260  		switch Oid {
   261  		case oid.T_float4:
   262  			b.putInt32(4)
   263  			b.putInt32(int32(math.Float32bits(float32(*v))))
   264  		case oid.T_float8:
   265  			b.putInt32(8)
   266  			b.putInt64(int64(math.Float64bits(float64(*v))))
   267  		default:
   268  			b.setError(errors.Errorf("unsupported float oid: %v", Oid))
   269  		}
   270  
   271  	case *tree.DDecimal:
   272  		if v.Form != apd.Finite {
   273  			b.putInt32(8)
   274  			// 0 digits.
   275  			b.putInt32(0)
   276  			// https://github.com/postgres/postgres/blob/ffa4cbd623dd69f9fa99e5e92426928a5782cf1a/src/backend/utils/adt/numeric.c#L169
   277  			b.write([]byte{0xc0, 0, 0, 0})
   278  
   279  			if v.Form == apd.Infinite {
   280  				// TODO(mjibson): #32489
   281  				// The above encoding is not correct for Infinity, but since that encoding
   282  				// doesn't exist in postgres, it's unclear what to do. For now use the NaN
   283  				// encoding and count it to see if anyone even needs this.
   284  				telemetry.Inc(sqltelemetry.BinaryDecimalInfinityCounter)
   285  			}
   286  
   287  			return
   288  		}
   289  
   290  		alloc := struct {
   291  			pgNum pgwirebase.PGNumeric
   292  
   293  			bigI big.Int
   294  		}{
   295  			pgNum: pgwirebase.PGNumeric{
   296  				// Since we use 2000 as the exponent limits in tree.DecimalCtx, this
   297  				// conversion should not overflow.
   298  				Dscale: int16(-v.Exponent),
   299  			},
   300  		}
   301  
   302  		if v.Sign() >= 0 {
   303  			alloc.pgNum.Sign = pgwirebase.PGNumericPos
   304  		} else {
   305  			alloc.pgNum.Sign = pgwirebase.PGNumericNeg
   306  		}
   307  
   308  		isZero := func(r rune) bool {
   309  			return r == '0'
   310  		}
   311  
   312  		// Mostly cribbed from libpqtypes' str2num.
   313  		digits := strings.TrimLeftFunc(alloc.bigI.Abs(&v.Coeff).String(), isZero)
   314  		dweight := len(digits) - int(alloc.pgNum.Dscale) - 1
   315  		digits = strings.TrimRightFunc(digits, isZero)
   316  
   317  		if dweight >= 0 {
   318  			alloc.pgNum.Weight = int16((dweight+1+pgwirebase.PGDecDigits-1)/pgwirebase.PGDecDigits - 1)
   319  		} else {
   320  			alloc.pgNum.Weight = int16(-((-dweight-1)/pgwirebase.PGDecDigits + 1))
   321  		}
   322  		offset := (int(alloc.pgNum.Weight)+1)*pgwirebase.PGDecDigits - (dweight + 1)
   323  		alloc.pgNum.Ndigits = int16((len(digits) + offset + pgwirebase.PGDecDigits - 1) / pgwirebase.PGDecDigits)
   324  
   325  		if len(digits) == 0 {
   326  			offset = 0
   327  			alloc.pgNum.Ndigits = 0
   328  			alloc.pgNum.Weight = 0
   329  		}
   330  
   331  		digitIdx := -offset
   332  
   333  		nextDigit := func() int16 {
   334  			var ndigit int16
   335  			for nextDigitIdx := digitIdx + pgwirebase.PGDecDigits; digitIdx < nextDigitIdx; digitIdx++ {
   336  				ndigit *= 10
   337  				if digitIdx >= 0 && digitIdx < len(digits) {
   338  					ndigit += int16(digits[digitIdx] - '0')
   339  				}
   340  			}
   341  			return ndigit
   342  		}
   343  
   344  		b.putInt32(int32(2 * (4 + alloc.pgNum.Ndigits)))
   345  		b.putInt16(alloc.pgNum.Ndigits)
   346  		b.putInt16(alloc.pgNum.Weight)
   347  		b.putInt16(int16(alloc.pgNum.Sign))
   348  		b.putInt16(alloc.pgNum.Dscale)
   349  
   350  		for digitIdx < len(digits) {
   351  			b.putInt16(nextDigit())
   352  		}
   353  
   354  	case *tree.DBytes:
   355  		b.putInt32(int32(len(*v)))
   356  		b.write([]byte(*v))
   357  
   358  	case *tree.DUuid:
   359  		b.putInt32(16)
   360  		b.write(v.GetBytes())
   361  
   362  	case *tree.DIPAddr:
   363  		// We calculate the Postgres binary format for an IPAddr. For the spec see,
   364  		// https://github.com/postgres/postgres/blob/81c5e46c490e2426db243eada186995da5bb0ba7/src/backend/utils/adt/network.c#L144
   365  		// The pgBinary encoding is as follows:
   366  		//  The int32 length of the following bytes.
   367  		//  The family byte.
   368  		//  The mask size byte.
   369  		//  A 0 byte for is_cidr. It's ignored on the postgres frontend.
   370  		//  The length of our IP bytes.
   371  		//  The IP bytes.
   372  		const pgIPAddrBinaryHeaderSize = 4
   373  		if v.Family == ipaddr.IPv4family {
   374  			b.putInt32(net.IPv4len + pgIPAddrBinaryHeaderSize)
   375  			b.writeByte(pgwirebase.PGBinaryIPv4family)
   376  			b.writeByte(v.Mask)
   377  			b.writeByte(0)
   378  			b.writeByte(byte(net.IPv4len))
   379  			err := v.Addr.WriteIPv4Bytes(b)
   380  			if err != nil {
   381  				b.setError(err)
   382  			}
   383  		} else if v.Family == ipaddr.IPv6family {
   384  			b.putInt32(net.IPv6len + pgIPAddrBinaryHeaderSize)
   385  			b.writeByte(pgwirebase.PGBinaryIPv6family)
   386  			b.writeByte(v.Mask)
   387  			b.writeByte(0)
   388  			b.writeByte(byte(net.IPv6len))
   389  			err := v.Addr.WriteIPv6Bytes(b)
   390  			if err != nil {
   391  				b.setError(err)
   392  			}
   393  		} else {
   394  			b.setError(errors.Errorf("error encoding inet to pgBinary: %v", v.IPAddr))
   395  		}
   396  
   397  	case *tree.DEnum:
   398  		b.writeLengthPrefixedString(v.LogicalRep)
   399  
   400  	case *tree.DString:
   401  		b.writeLengthPrefixedString(string(*v))
   402  
   403  	case *tree.DCollatedString:
   404  		b.writeLengthPrefixedString(v.Contents)
   405  
   406  	case *tree.DTimestamp:
   407  		b.putInt32(8)
   408  		b.putInt64(timeToPgBinary(v.Time, nil))
   409  
   410  	case *tree.DTimestampTZ:
   411  		b.putInt32(8)
   412  		b.putInt64(timeToPgBinary(v.Time, sessionLoc))
   413  
   414  	case *tree.DDate:
   415  		b.putInt32(4)
   416  		b.putInt32(v.PGEpochDays())
   417  
   418  	case *tree.DTime:
   419  		b.putInt32(8)
   420  		b.putInt64(int64(*v))
   421  
   422  	case *tree.DTimeTZ:
   423  		b.putInt32(12)
   424  		b.putInt64(int64(v.TimeOfDay))
   425  		b.putInt32(v.OffsetSecs)
   426  
   427  	case *tree.DInterval:
   428  		b.putInt32(16)
   429  		b.putInt64(v.Nanos() / int64(time.Microsecond/time.Nanosecond))
   430  		b.putInt32(int32(v.Days))
   431  		b.putInt32(int32(v.Months))
   432  
   433  	case *tree.DTuple:
   434  		// TODO(andrei): We shouldn't be allocating a new buffer for every array.
   435  		subWriter := newWriteBuffer(nil /* bytecount */)
   436  		// Put the number of datums.
   437  		subWriter.putInt32(int32(len(v.D)))
   438  		for _, elem := range v.D {
   439  			oid := elem.ResolvedType().Oid()
   440  			subWriter.putInt32(int32(oid))
   441  			subWriter.writeBinaryDatum(ctx, elem, sessionLoc, oid)
   442  		}
   443  		b.writeLengthPrefixedBuffer(&subWriter.wrapped)
   444  
   445  	case *tree.DGeography:
   446  		b.putInt32(int32(len(v.EWKB())))
   447  		b.write(v.EWKB())
   448  
   449  	case *tree.DGeometry:
   450  		b.putInt32(int32(len(v.EWKB())))
   451  		b.write(v.EWKB())
   452  
   453  	case *tree.DArray:
   454  		if v.ParamTyp.Family() == types.ArrayFamily {
   455  			b.setError(unimplemented.NewWithIssueDetail(32552,
   456  				"binenc", "unsupported binary serialization of multidimensional arrays"))
   457  			return
   458  		}
   459  		// TODO(andrei): We shouldn't be allocating a new buffer for every array.
   460  		subWriter := newWriteBuffer(nil /* bytecount */)
   461  		// Put the number of dimensions. We currently support 1d arrays only.
   462  		var ndims int32 = 1
   463  		if v.Len() == 0 {
   464  			ndims = 0
   465  		}
   466  		subWriter.putInt32(ndims)
   467  		hasNulls := 0
   468  		if v.HasNulls {
   469  			hasNulls = 1
   470  		}
   471  		oid := v.ParamTyp.Oid()
   472  		subWriter.putInt32(int32(hasNulls))
   473  		subWriter.putInt32(int32(oid))
   474  		if v.Len() > 0 {
   475  			subWriter.putInt32(int32(v.Len()))
   476  			// Lower bound, we only support a lower bound of 1.
   477  			subWriter.putInt32(1)
   478  			for _, elem := range v.Array {
   479  				subWriter.writeBinaryDatum(ctx, elem, sessionLoc, oid)
   480  			}
   481  		}
   482  		b.writeLengthPrefixedBuffer(&subWriter.wrapped)
   483  	case *tree.DJSON:
   484  		s := v.JSON.String()
   485  		b.putInt32(int32(len(s) + 1))
   486  		// Postgres version number, as of writing, `1` is the only valid value.
   487  		b.writeByte(1)
   488  		b.writeString(s)
   489  	case *tree.DOid:
   490  		b.putInt32(4)
   491  		b.putInt32(int32(v.DInt))
   492  	default:
   493  		b.setError(errors.AssertionFailedf("unsupported type %T", d))
   494  	}
   495  }
   496  
   497  const (
   498  	pgTimeFormat              = "15:04:05.999999"
   499  	pgTimeTZFormat            = pgTimeFormat + "-07:00"
   500  	pgDateFormat              = "2006-01-02"
   501  	pgTimeStampFormatNoOffset = pgDateFormat + " " + pgTimeFormat
   502  	pgTimeStampFormat         = pgTimeStampFormatNoOffset + "-07:00"
   503  	pgTime2400Format          = "24:00:00"
   504  )
   505  
   506  // formatTime formats t into a format lib/pq understands, appending to the
   507  // provided tmp buffer and reallocating if needed. The function will then return
   508  // the resulting buffer.
   509  func formatTime(t timeofday.TimeOfDay, tmp []byte) []byte {
   510  	// time.Time's AppendFormat does not recognize 2400, so special case it accordingly.
   511  	if t == timeofday.Time2400 {
   512  		return []byte(pgTime2400Format)
   513  	}
   514  	return t.ToTime().AppendFormat(tmp, pgTimeFormat)
   515  }
   516  
   517  // formatTimeTZ formats t into a format lib/pq understands, appending to the
   518  // provided tmp buffer and reallocating if needed. The function will then return
   519  // the resulting buffer.
   520  // Note it does not understand the "second" component of the offset as lib/pq
   521  // cannot parse it.
   522  func formatTimeTZ(t timetz.TimeTZ, tmp []byte) []byte {
   523  	ret := t.ToTime().AppendFormat(tmp, pgTimeTZFormat)
   524  	// time.Time's AppendFormat does not recognize 2400, so special case it accordingly.
   525  	if t.TimeOfDay == timeofday.Time2400 {
   526  		// It instead reads 00:00:00. Replace that text.
   527  		var newRet []byte
   528  		newRet = append(newRet, pgTime2400Format...)
   529  		newRet = append(newRet, ret[len(pgTime2400Format):]...)
   530  		ret = newRet
   531  	}
   532  	return ret
   533  }
   534  
   535  func formatTs(t time.Time, offset *time.Location, tmp []byte) (b []byte) {
   536  	var format string
   537  	if offset != nil {
   538  		format = pgTimeStampFormat
   539  	} else {
   540  		format = pgTimeStampFormatNoOffset
   541  	}
   542  	return formatTsWithFormat(format, t, offset, tmp)
   543  }
   544  
   545  // formatTsWithFormat formats t with an optional offset into a format
   546  // lib/pq understands, appending to the provided tmp buffer and
   547  // reallocating if needed. The function will then return the resulting
   548  // buffer. formatTsWithFormat is mostly cribbed from github.com/lib/pq.
   549  func formatTsWithFormat(format string, t time.Time, offset *time.Location, tmp []byte) (b []byte) {
   550  	// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
   551  	// minus sign preferred by Go.
   552  	// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
   553  	if offset != nil {
   554  		t = t.In(offset)
   555  	}
   556  
   557  	bc := false
   558  	if t.Year() <= 0 {
   559  		// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
   560  		t = t.AddDate((-t.Year())*2+1, 0, 0)
   561  		bc = true
   562  	}
   563  
   564  	b = t.AppendFormat(tmp, format)
   565  	if bc {
   566  		b = append(b, " BC"...)
   567  	}
   568  	return b
   569  }
   570  
   571  // timeToPgBinary calculates the Postgres binary format for a timestamp. The timestamp
   572  // is represented as the number of microseconds between the given time and Jan 1, 2000
   573  // (dubbed the PGEpochJDate), stored within an int64.
   574  func timeToPgBinary(t time.Time, offset *time.Location) int64 {
   575  	if offset != nil {
   576  		t = t.In(offset)
   577  	} else {
   578  		t = t.UTC()
   579  	}
   580  	return duration.DiffMicros(t, pgwirebase.PGEpochJDate)
   581  }