github.com/jackc/pgx/v5@v5.5.5/pgtype/polygon.go (about)

     1  package pgtype
     2  
     3  import (
     4  	"database/sql/driver"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"math"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/jackc/pgx/v5/internal/pgio"
    12  )
    13  
    14  type PolygonScanner interface {
    15  	ScanPolygon(v Polygon) error
    16  }
    17  
    18  type PolygonValuer interface {
    19  	PolygonValue() (Polygon, error)
    20  }
    21  
    22  type Polygon struct {
    23  	P     []Vec2
    24  	Valid bool
    25  }
    26  
    27  func (p *Polygon) ScanPolygon(v Polygon) error {
    28  	*p = v
    29  	return nil
    30  }
    31  
    32  func (p Polygon) PolygonValue() (Polygon, error) {
    33  	return p, nil
    34  }
    35  
    36  // Scan implements the database/sql Scanner interface.
    37  func (p *Polygon) Scan(src any) error {
    38  	if src == nil {
    39  		*p = Polygon{}
    40  		return nil
    41  	}
    42  
    43  	switch src := src.(type) {
    44  	case string:
    45  		return scanPlanTextAnyToPolygonScanner{}.Scan([]byte(src), p)
    46  	}
    47  
    48  	return fmt.Errorf("cannot scan %T", src)
    49  }
    50  
    51  // Value implements the database/sql/driver Valuer interface.
    52  func (p Polygon) Value() (driver.Value, error) {
    53  	if !p.Valid {
    54  		return nil, nil
    55  	}
    56  
    57  	buf, err := PolygonCodec{}.PlanEncode(nil, 0, TextFormatCode, p).Encode(p, nil)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return string(buf), err
    63  }
    64  
    65  type PolygonCodec struct{}
    66  
    67  func (PolygonCodec) FormatSupported(format int16) bool {
    68  	return format == TextFormatCode || format == BinaryFormatCode
    69  }
    70  
    71  func (PolygonCodec) PreferredFormat() int16 {
    72  	return BinaryFormatCode
    73  }
    74  
    75  func (PolygonCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
    76  	if _, ok := value.(PolygonValuer); !ok {
    77  		return nil
    78  	}
    79  
    80  	switch format {
    81  	case BinaryFormatCode:
    82  		return encodePlanPolygonCodecBinary{}
    83  	case TextFormatCode:
    84  		return encodePlanPolygonCodecText{}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  type encodePlanPolygonCodecBinary struct{}
    91  
    92  func (encodePlanPolygonCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
    93  	polygon, err := value.(PolygonValuer).PolygonValue()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	if !polygon.Valid {
    99  		return nil, nil
   100  	}
   101  
   102  	buf = pgio.AppendInt32(buf, int32(len(polygon.P)))
   103  
   104  	for _, p := range polygon.P {
   105  		buf = pgio.AppendUint64(buf, math.Float64bits(p.X))
   106  		buf = pgio.AppendUint64(buf, math.Float64bits(p.Y))
   107  	}
   108  
   109  	return buf, nil
   110  }
   111  
   112  type encodePlanPolygonCodecText struct{}
   113  
   114  func (encodePlanPolygonCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
   115  	polygon, err := value.(PolygonValuer).PolygonValue()
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	if !polygon.Valid {
   121  		return nil, nil
   122  	}
   123  
   124  	buf = append(buf, '(')
   125  
   126  	for i, p := range polygon.P {
   127  		if i > 0 {
   128  			buf = append(buf, ',')
   129  		}
   130  		buf = append(buf, fmt.Sprintf(`(%s,%s)`,
   131  			strconv.FormatFloat(p.X, 'f', -1, 64),
   132  			strconv.FormatFloat(p.Y, 'f', -1, 64),
   133  		)...)
   134  	}
   135  
   136  	buf = append(buf, ')')
   137  
   138  	return buf, nil
   139  }
   140  
   141  func (PolygonCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
   142  
   143  	switch format {
   144  	case BinaryFormatCode:
   145  		switch target.(type) {
   146  		case PolygonScanner:
   147  			return scanPlanBinaryPolygonToPolygonScanner{}
   148  		}
   149  	case TextFormatCode:
   150  		switch target.(type) {
   151  		case PolygonScanner:
   152  			return scanPlanTextAnyToPolygonScanner{}
   153  		}
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  type scanPlanBinaryPolygonToPolygonScanner struct{}
   160  
   161  func (scanPlanBinaryPolygonToPolygonScanner) Scan(src []byte, dst any) error {
   162  	scanner := (dst).(PolygonScanner)
   163  
   164  	if src == nil {
   165  		return scanner.ScanPolygon(Polygon{})
   166  	}
   167  
   168  	if len(src) < 5 {
   169  		return fmt.Errorf("invalid length for polygon: %v", len(src))
   170  	}
   171  
   172  	pointCount := int(binary.BigEndian.Uint32(src))
   173  	rp := 4
   174  
   175  	if 4+pointCount*16 != len(src) {
   176  		return fmt.Errorf("invalid length for Polygon with %d points: %v", pointCount, len(src))
   177  	}
   178  
   179  	points := make([]Vec2, pointCount)
   180  	for i := 0; i < len(points); i++ {
   181  		x := binary.BigEndian.Uint64(src[rp:])
   182  		rp += 8
   183  		y := binary.BigEndian.Uint64(src[rp:])
   184  		rp += 8
   185  		points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)}
   186  	}
   187  
   188  	return scanner.ScanPolygon(Polygon{
   189  		P:     points,
   190  		Valid: true,
   191  	})
   192  }
   193  
   194  type scanPlanTextAnyToPolygonScanner struct{}
   195  
   196  func (scanPlanTextAnyToPolygonScanner) Scan(src []byte, dst any) error {
   197  	scanner := (dst).(PolygonScanner)
   198  
   199  	if src == nil {
   200  		return scanner.ScanPolygon(Polygon{})
   201  	}
   202  
   203  	if len(src) < 7 {
   204  		return fmt.Errorf("invalid length for Polygon: %v", len(src))
   205  	}
   206  
   207  	points := make([]Vec2, 0)
   208  
   209  	str := string(src[2:])
   210  
   211  	for {
   212  		end := strings.IndexByte(str, ',')
   213  		x, err := strconv.ParseFloat(str[:end], 64)
   214  		if err != nil {
   215  			return err
   216  		}
   217  
   218  		str = str[end+1:]
   219  		end = strings.IndexByte(str, ')')
   220  
   221  		y, err := strconv.ParseFloat(str[:end], 64)
   222  		if err != nil {
   223  			return err
   224  		}
   225  
   226  		points = append(points, Vec2{x, y})
   227  
   228  		if end+3 < len(str) {
   229  			str = str[end+3:]
   230  		} else {
   231  			break
   232  		}
   233  	}
   234  
   235  	return scanner.ScanPolygon(Polygon{P: points, Valid: true})
   236  }
   237  
   238  func (c PolygonCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
   239  	return codecDecodeToTextFormat(c, m, oid, format, src)
   240  }
   241  
   242  func (c PolygonCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
   243  	if src == nil {
   244  		return nil, nil
   245  	}
   246  
   247  	var polygon Polygon
   248  	err := codecScan(c, m, oid, format, src, &polygon)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	return polygon, nil
   253  }