Skip to content

Commit 3145ed0

Browse files
craig[bot]yuzefovich
andcommitted
Merge #148719
148719: pgwire: support decoding VECTOR and BOX2D from binary r=yuzefovich a=yuzefovich This commit fixes an oversight where we forgot to add decoding support for PGVector and Box2D from binary format (of PGWire extended protocol). The encoding was added in c783266 and 7fa9129, respectively. Note that for Box2D postgres doesn't support the binary format (I get an error "no binary output function available for type box2d"), but since we already introduced encoding, it seems reasonable to add decoding too. Fixes: #147844. Release note (bug fix): CockroachDB can now decode VECTOR and BOX2D types from Binary format of PGWire Extended Protocol. Co-authored-by: Yahor Yuzefovich <[email protected]>
2 parents 6ff7857 + 009ce67 commit 3145ed0

File tree

5 files changed

+90
-1
lines changed

5 files changed

+90
-1
lines changed

pkg/sql/pgwire/pgwirebase/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ go_library(
2020
visibility = ["//visibility:public"],
2121
deps = [
2222
"//pkg/geo",
23+
"//pkg/geo/geopb",
2324
"//pkg/settings",
2425
"//pkg/sql/catalog/colinfo",
2526
"//pkg/sql/lex",

pkg/sql/pgwire/pgwirebase/encoding.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"unicode/utf8"
1919

2020
"github.com/cockroachdb/cockroach/pkg/geo"
21+
"github.com/cockroachdb/cockroach/pkg/geo/geopb"
2122
"github.com/cockroachdb/cockroach/pkg/settings"
2223
"github.com/cockroachdb/cockroach/pkg/sql/lex"
2324
"github.com/cockroachdb/cockroach/pkg/sql/oidext"
@@ -817,6 +818,43 @@ func DecodeDatum(
817818
return nil, err
818819
}
819820
return tree.NewDTSVector(ret), nil
821+
case oidext.T_pgvector:
822+
// PG binary format is
823+
// 2 bytes for dimensions
824+
// 2 bytes for unused, and
825+
// 4 bytes for each float4.
826+
if len(b) < 4 {
827+
return nil, pgerror.Newf(pgcode.Syntax, "vector requires at least 4 bytes for binary format")
828+
}
829+
dim := int(binary.BigEndian.Uint16(b))
830+
b = b[4:]
831+
if dim > vector.MaxDim {
832+
return nil, vector.MaxDimExceededErr
833+
}
834+
if len(b) < 4*dim {
835+
return nil, pgerror.Newf(pgcode.Syntax, "vector with %d dimensions requires %d bytes for binary format", dim, 4*dim)
836+
}
837+
v := make(vector.T, dim)
838+
for i := 0; i < dim; i++ {
839+
v[i] = math.Float32frombits(binary.BigEndian.Uint32(b))
840+
b = b[4:]
841+
}
842+
return tree.NewDPGVector(v), nil
843+
case oidext.T_box2d:
844+
// Expect 8 bytes for each of LoX, HiX, LoY, HiY.
845+
if len(b) < 32 {
846+
return nil, pgerror.Newf(pgcode.Syntax, "box2d requires at least 32 bytes for binary format")
847+
}
848+
loX := math.Float64frombits(binary.BigEndian.Uint64(b[0:8]))
849+
hiX := math.Float64frombits(binary.BigEndian.Uint64(b[8:16]))
850+
loY := math.Float64frombits(binary.BigEndian.Uint64(b[16:24]))
851+
hiY := math.Float64frombits(binary.BigEndian.Uint64(b[24:32]))
852+
box := geo.CartesianBoundingBox{
853+
BoundingBox: geopb.BoundingBox{
854+
LoX: loX, HiX: hiX, LoY: loY, HiY: hiY,
855+
},
856+
}
857+
return da.NewDBox2D(tree.DBox2D{CartesianBoundingBox: box}), nil
820858
case oidext.T_geometry:
821859
v, err := geo.ParseGeometryFromEWKB(b)
822860
if err != nil {

pkg/sql/pgwire/testdata/pgtest/box2d

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# "ResultFormatCodes": [1] = binary
2+
send
3+
Parse {"Name": "s", "Query": "SELECT 'BOX(1 0,1 0)'::BOX2D;"}
4+
Bind {"DestinationPortal": "p", "PreparedStatement": "s", "ResultFormatCodes": [1]}
5+
Execute {"Portal": "p"}
6+
Sync
7+
----
8+
9+
until
10+
ReadyForQuery
11+
----
12+
{"Type":"ParseComplete"}
13+
{"Type":"BindComplete"}
14+
{"Type":"DataRow","Values":[{"binary":"3ff00000000000003ff000000000000000000000000000000000000000000000"}]}
15+
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
16+
{"Type":"ReadyForQuery","TxStatus":"I"}
17+
18+
send
19+
Parse {"Query": "SELECT $1::BOX2D"}
20+
Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"3ff00000000000003ff000000000000000000000000000000000000000000000"}]}
21+
Execute
22+
Sync
23+
----
24+
25+
until
26+
ReadyForQuery
27+
----
28+
{"Type":"ParseComplete"}
29+
{"Type":"BindComplete"}
30+
{"Type":"DataRow","Values":[{"text":"BOX(1 0,1 0)"}]}
31+
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
32+
{"Type":"ReadyForQuery","TxStatus":"I"}

pkg/sql/pgwire/testdata/pgtest/pgvector

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,19 @@ ReadyForQuery
4545
{"Type":"DataRow","Values":[{"binary":"000200004000000040400000"}]}
4646
{"Type":"CommandComplete","CommandTag":"SELECT 2"}
4747
{"Type":"ReadyForQuery","TxStatus":"I"}
48+
49+
send
50+
Parse {"Query": "SELECT $1::VECTOR"}
51+
Bind {"ParameterFormatCodes": [1], "Parameters": [{"binary":"000100003f800000"}]}
52+
Execute
53+
Sync
54+
----
55+
56+
until
57+
ReadyForQuery
58+
----
59+
{"Type":"ParseComplete"}
60+
{"Type":"BindComplete"}
61+
{"Type":"DataRow","Values":[{"text":"[1]"}]}
62+
{"Type":"CommandComplete","CommandTag":"SELECT 1"}
63+
{"Type":"ReadyForQuery","TxStatus":"I"}

pkg/util/vector/vector.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
// MaxDim is the maximum number of dimensions a vector can have.
2222
const MaxDim = 16000
2323

24+
var MaxDimExceededErr = pgerror.Newf(pgcode.ProgramLimitExceeded, "vector cannot have more than %d dimensions", MaxDim)
25+
2426
// T is the type of a PGVector-like vector.
2527
type T []float32
2628

@@ -38,7 +40,7 @@ func ParseVector(input string) (T, error) {
3840
parts := strings.Split(input, ",")
3941

4042
if len(parts) > MaxDim {
41-
return T{}, pgerror.Newf(pgcode.ProgramLimitExceeded, "vector cannot have more than %d dimensions", MaxDim)
43+
return T{}, MaxDimExceededErr
4244
}
4345

4446
vector := make([]float32, len(parts))

0 commit comments

Comments
 (0)