...

Source file src/github.com/jackc/pgx/v5/pgtype/path.go

Documentation: github.com/jackc/pgx/v5/pgtype

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

View as plain text