Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi range support #290

Merged
merged 16 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/client/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func defaultBackoff(attempt int) time.Duration {
return time.Duration(backoff+jitter) * time.Millisecond
}

// RetryCondition represents scenarios that can caused a transaction
// RetryCondition represents scenarios that can cause a transaction
// run in Tx() methods to be retried.
type RetryCondition int

Expand Down
45 changes: 45 additions & 0 deletions internal/client/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7806,3 +7806,48 @@ func TestSendAndReceiveRangeLocalDate(t *testing.T) {
})
}
}

func serverHasMultiRange(t *testing.T) bool {
var hasMultiRange bool
err := client.QuerySingle(
context.Background(),
`SELECT count((
SELECT names := schema::ObjectType.name
FILTER names = 'schema::MultiRange'
)) = 1`,
&hasMultiRange,
)
require.NoError(t, err)
return hasMultiRange
}

func TestSendAndReceiveInt32MultiRange(t *testing.T) {
if !serverHasMultiRange(t) {
t.Skip("server lacks std::MultiRange support")
}

ctx := context.Background()

var result []types.RangeInt32

multiRange := make([]types.RangeInt32, 2)

multiRange[0] = types.NewRangeInt32(
types.NewOptionalInt32(1),
types.NewOptionalInt32(1),
true,
false,
)

multiRange[1] = types.NewRangeInt32(
types.NewOptionalInt32(1),
types.NewOptionalInt32(10),
true,
false,
)

query := "SELECT <array<range<int32>>>$0"
diksipav marked this conversation as resolved.
Show resolved Hide resolved
err := client.QuerySingle(ctx, query, &result, multiRange)
require.NoError(t, err)
assert.Equal(t, multiRange, result)
}
2 changes: 2 additions & 0 deletions internal/codecs/codecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func BuildEncoder(
return buildArrayEncoder(desc, version)
case descriptor.Range:
return buildRangeEncoder(desc, version)
case descriptor.MultiRange:
return buildMultiRangeEncoder(desc, version)
diksipav marked this conversation as resolved.
Show resolved Hide resolved
default:
return nil, fmt.Errorf(
"building encoder: unknown descriptor type 0x%x",
Expand Down
185 changes: 185 additions & 0 deletions internal/codecs/multirange.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// This source file is part of the EdgeDB open source project.
//
// Copyright EdgeDB Inc. and the EdgeDB authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package codecs

import (
"fmt"
"reflect"
"unsafe"

"github.com/edgedb/edgedb-go/internal"
"github.com/edgedb/edgedb-go/internal/buff"
"github.com/edgedb/edgedb-go/internal/descriptor"
types "github.com/edgedb/edgedb-go/internal/edgedbtypes"
)

func buildMultiRangeEncoder(
desc descriptor.Descriptor,
version internal.ProtocolVersion,
) (Encoder, error) {
child, err := BuildEncoder(desc.Fields[0].Desc, version)

if err != nil {
return nil, err
}

return &multiRangeEncoder{desc.ID, child}, nil
}

func buildMultiRangeEncoderV2(
desc *descriptor.V2,
version internal.ProtocolVersion,
) (Encoder, error) {
child, err := BuildEncoderV2(&desc.Fields[0].Desc, version)

if err != nil {
return nil, err
}

return &multiRangeEncoder{desc.ID, child}, nil
}

type multiRangeEncoder struct {
id types.UUID
child Encoder
}

func (c *multiRangeEncoder) DescriptorID() types.UUID { return c.id }

func (c *multiRangeEncoder) Encode(
w *buff.Writer,
val interface{},
path Path,
required bool, // todo do we need this?
) error {
in := reflect.ValueOf(val)
if in.Kind() != reflect.Slice {
return fmt.Errorf(
"expected %v to be a slice got: %T", path, val,
)
}

if in.IsNil() && required { // todo do we need this?
return missingValueError(val, path)
}

if in.IsNil() {
w.PushUint32(0xffffffff)
return nil
}

elmCount := in.Len()

w.BeginBytes()
w.PushUint32(uint32(elmCount))

var err error
for i := 0; i < elmCount; i++ {
err = c.child.Encode(
w,
in.Index(i).Interface(),
path.AddIndex(i),
true,
)
if err != nil {
return err
}
}

w.EndBytes()
return nil
}

func buildMultiRangeDecoder(
desc descriptor.Descriptor,
typ reflect.Type,
path Path,
) (Decoder, error) {
if typ.Kind() != reflect.Slice {
return nil, fmt.Errorf(
"expected %v to be a Slice, got %v", path, typ.Kind(),
)
}

child, err := BuildDecoder(desc.Fields[0].Desc, typ.Elem(), path)
if err != nil {
return nil, err
}

return &multiRangeDecoder{desc.ID, child, typ, calcStep(typ.Elem())}, nil
}

func buildMultiRangeDecoderV2(
desc *descriptor.V2,
typ reflect.Type,
path Path,
) (Decoder, error) {
if typ.Kind() != reflect.Slice {
return nil, fmt.Errorf(
"expected %v to be a Slice, got %v", path, typ.Kind(),
)
}

child, err := BuildDecoderV2(&desc.Fields[0].Desc, typ.Elem(), path)
if err != nil {
return nil, err
}

return &multiRangeDecoder{desc.ID, child, typ, calcStep(typ.Elem())}, nil
}

type multiRangeDecoder struct {
id types.UUID
child Decoder
typ reflect.Type

// step is the element width in bytes for a go array of type `MultiRange.typ`.
step int
}

func (c *multiRangeDecoder) DescriptorID() types.UUID { return c.id }

func (c *multiRangeDecoder) Decode(r *buff.Reader, out unsafe.Pointer) error {
upper := int32(r.PopUint32())
n := int(upper)

slice := (*sliceHeader)(out)
setSliceLen(slice, c.typ, n)

for i := 0; i < n; i++ {
elmLen := r.PopUint32()
if elmLen == 0xffffffff {
continue
}

err := c.child.Decode(
r.PopSlice(elmLen),
pAdd(slice.Data, uintptr(i*c.step)),
)
if err != nil {
return err
}
}
return nil
}

func (c *multiRangeDecoder) DecodeMissing(out unsafe.Pointer) {
slice := (*sliceHeader)(out)
slice.Data = nilPointer
slice.Len = 0
slice.Cap = 0
}
12 changes: 12 additions & 0 deletions internal/descriptor/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ const (

// Compound represents the compound descriptor type.
Compound

// MultiRange represents the multi range descriptor type.
MultiRange
)

// Descriptor is a type descriptor
Expand Down Expand Up @@ -143,6 +146,15 @@ func Pop(
desc = Descriptor{typ, id, []*Field{{
Desc: descriptors[r.PopUint16()],
}}}
case MultiRange:
fields := []*Field{{
Desc: descriptors[r.PopUint16()],
}}
err := assertArrayDimensions(r)
if err != nil {
return Descriptor{}, err
}
desc = Descriptor{typ, id, fields}
default:
if 0x80 <= typ && typ <= 0xff {
// ignore unknown type annotations
Expand Down
12 changes: 12 additions & 0 deletions internal/descriptor/descriptor_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ func PopV2(
}
fields := scalarFields2pX(r, descriptorsV2, unionOperation)
desc = V2{Compound, id, name, true, nil, fields}
case MultiRange:
name := r.PopString()
r.PopUint8() // schema_defined
ancestors := scalarFields2pX(r, descriptorsV2, false)
fields := []*FieldV2{{
Desc: descriptorsV2[r.PopUint16()],
}}
err := assertArrayDimensions(r)
if err != nil {
return V2{}, err
}
diksipav marked this conversation as resolved.
Show resolved Hide resolved
desc = V2{MultiRange, id, name, true, ancestors, fields}
default:
if 0x80 <= typ && typ <= 0xff {
// ignore unknown type annotations
Expand Down
4 changes: 2 additions & 2 deletions internal/edgedbtypes/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,12 +1156,12 @@ func (dd DateDuration) String() string {
return strings.Join(buf, "")
}

// MarshalText returns rd marshaled as text.
// MarshalText returns dd marshaled as text.
func (dd DateDuration) MarshalText() ([]byte, error) {
return []byte(dd.String()), nil
}

// UnmarshalText unmarshals bytes into *rd.
// UnmarshalText unmarshals bytes into *dd.
func (dd *DateDuration) UnmarshalText(b []byte) error {
str := string(b)
if !strings.HasPrefix(str, "P") {
Expand Down